diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,46 @@
 
 ---
 
+## [0.17.3] - 2026-08-01
+
+Point release on the 0.17.x line, focused on **release-artifact integrity**. The 0.17.2 release shipped
+two defects that every existing check missed because they validated only the generated `dist/` tree, never
+the packaged artifact: the `hydra-build` sdist shipped 3 of 8 modules, and the published Java `hydra-kernel`
+jar was a whole [#417](https://github.com/CategoricalData/hydra/issues/417) rename behind. This release adds
+an artifact-level publish-completeness gate that inspects the actual uploaded archive, closing that class of
+defect across all five registries.
+
+### Highlights
+
+- **Artifact-content completeness gate** ([#621](https://github.com/CategoricalData/hydra/issues/621)):
+  every publish path (Hackage sdist, Maven-Java jar, Maven-Scala jar, PyPI wheel, npm tarball) now inspects
+  the *packaged* archive and hard-fails if any module a package's manifest declares is missing. The gate also
+  asserts the post-#417 class names are present in the Java kernel jar.
+
+### Bug fixes
+
+- **PyPI wheels dropped non-`hydra.*` roots** ([#621](https://github.com/CategoricalData/hydra/issues/621)):
+  the wheel packaging hardcoded `packages = ["src/main/python/hydra"]`, silently omitting `hydra-pg`'s
+  `com.gdblab.*` and `openGql.*` modules. The generator now ships every emitted top-level root.
+
+### Improvements
+
+- **CI guard for dist-tree completeness** ([#524](https://github.com/CategoricalData/hydra/issues/524)):
+  asserts every manifest `mainModules` namespace is emitted into `dist/haskell`, catching the 0.17.2
+  `hydra-build` truncation at the tree level (the artifact gate is the packaged-archive counterpart).
+- **Manifest generation extracted** into `Hydra.ManifestGeneration`, and the structurally-unneeded #607
+  sed shims dropped ([#622](https://github.com/CategoricalData/hydra/issues/622)).
+- **Release-verification signing fix** ([#441](https://github.com/CategoricalData/hydra/issues/441)):
+  sign the reproducible uncompressed `.tar` and verify the `.asc` against the GitHub Release asset.
+- **Java CI signing gate** ([#591](https://github.com/CategoricalData/hydra/issues/591)): `publishToMavenLocal`
+  skips gpg signing when no key is present.
+
+### Internal
+
+- Version bump to 0.17.3; `hostVersion` advanced to 0.17.2 and the temporary #417 Java/Python local-host
+  shims removed now that 0.17.2 is published on the registries
+  ([#417](https://github.com/CategoricalData/hydra/issues/417)).
+
 ## [0.17.2] - 2026-07-28
 
 Point release on the 0.17.x line. Themes: promotion of the generator's routing and manifest
diff --git a/hydra-scala.cabal b/hydra-scala.cabal
--- a/hydra-scala.cabal
+++ b/hydra-scala.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           hydra-scala
-version:        0.17.3
+version:        0.17.4
 synopsis:       Hydra's Scala coder: emit Scala source from Hydra modules
 description:    Hydra is an implementation of the LambdaGraph data model, which takes advantage of an isomorphism between labeled hypergraphs and typed lambda calculus: in Hydra, "graphs are programs, and programs are graphs". Scala support for Hydra
 category:       Data
@@ -40,7 +40,7 @@
   build-depends:
       base >=4.19.0 && <4.22
     , containers >=0.6.7 && <0.8
-    , hydra-jvm ==0.17.3
-    , hydra-kernel ==0.17.3
+    , hydra-jvm ==0.17.4
+    , hydra-kernel ==0.17.4
     , scientific >=0.3.7 && <0.4
   default-language: Haskell2010
diff --git a/src/main/haskell/Hydra/Dsl/Scala/Syntax.hs b/src/main/haskell/Hydra/Dsl/Scala/Syntax.hs
--- a/src/main/haskell/Hydra/Dsl/Scala/Syntax.hs
+++ b/src/main/haskell/Hydra/Dsl/Scala/Syntax.hs
@@ -5,7 +5,6 @@
 module Hydra.Dsl.Scala.Syntax where
 
 import qualified Hydra.Core as Core
-import qualified Hydra.Dsl.Core as DslCore
 import qualified Hydra.Scala.Syntax as Syntax
 import qualified Hydra.Typed as Typed
 import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
diff --git a/src/main/haskell/Hydra/Scala/Coder.hs b/src/main/haskell/Hydra/Scala/Coder.hs
--- a/src/main/haskell/Hydra/Scala/Coder.hs
+++ b/src/main/haskell/Hydra/Scala/Coder.hs
@@ -107,8 +107,8 @@
       _ -> Sets.empty
 
 -- | Construct a Scala package from a Hydra module and its definitions
-constructModule :: t0 -> Graph.Graph -> Packaging.Module -> [Packaging.Definition] -> Either Errors.Error Syntax.Pkg
-constructModule cx g mod defs =
+constructModule :: S.Set String -> t0 -> Graph.Graph -> Packaging.Module -> [Packaging.Definition] -> Either Errors.Error Syntax.Pkg
+constructModule overlaySubs cx g mod defs =
 
       let partitioned = Environment.partitionDefinitions defs
           typeDefs = Pairs.first partitioned
@@ -117,7 +117,7 @@
           pname = Syntax.NameData {
                 Syntax.nameDataValue = (Syntax.PredefString (Strings.join "." (Strings.splitOn "." nsName)))}
           pref = Syntax.RefDataName pname
-      in (Eithers.bind (Eithers.mapList (\td -> encodeTypeDefinition cx g td) typeDefs) (\typeDeclStats -> Eithers.bind (Eithers.mapList (\td -> encodeTermDefinition cx g td) termDefs) (\termDeclStats -> Eithers.bind (findImports cx g mod) (\imports -> Right (Syntax.Pkg {
+      in (Eithers.bind (Eithers.mapList (\td -> encodeTypeDefinition cx g td) typeDefs) (\typeDeclStats -> Eithers.bind (Eithers.mapList (\td -> encodeTermDefinition overlaySubs cx g td) termDefs) (\termDeclStats -> Eithers.bind (findImports overlaySubs cx g mod) (\imports -> Right (Syntax.Pkg {
         Syntax.pkgName = pname,
         Syntax.pkgRef = pref,
         Syntax.pkgStats = (Lists.concat [
@@ -202,8 +202,8 @@
       _ -> t)
 
 -- | Encode a case branch
-encodeCase :: t0 -> Graph.Graph -> M.Map Core.Name Core.Type -> Maybe Core.Name -> Core.CaseAlternative -> Either Errors.Error Syntax.Case
-encodeCase cx g ftypes sn f =
+encodeCase :: S.Set String -> t0 -> Graph.Graph -> M.Map Core.Name Core.Type -> Maybe Core.Name -> Core.CaseAlternative -> Either Errors.Error Syntax.Case
+encodeCase overlaySubs cx g ftypes sn f =
 
       let fname = Core.caseAlternativeName f
           fterm = Core.caseAlternativeHandler f
@@ -248,14 +248,14 @@
                     Syntax.extractPatFun = (Utils.sname (Utils.qualifyUnionFieldName "MATCHED." sn fname)),
                     Syntax.extractPatArgs = patArgs})
           applied = applyVar fterm v
-      in (Eithers.bind (encodeTerm cx g applied) (\body -> Right (Syntax.Case {
+      in (Eithers.bind (encodeTerm overlaySubs cx g applied) (\body -> Right (Syntax.Case {
         Syntax.casePat = pat,
         Syntax.caseCond = Nothing,
         Syntax.caseBody = body})))
 
 -- | Encode a complex term definition with proper parameter types from the type signature
-encodeComplexTermDef :: t0 -> Graph.Graph -> String -> Core.Term -> Core.Type -> Either Errors.Error Syntax.Stat
-encodeComplexTermDef cx g lname term typ =
+encodeComplexTermDef :: S.Set String -> t0 -> Graph.Graph -> String -> Core.Term -> Core.Type -> Either Errors.Error Syntax.Stat
+encodeComplexTermDef overlaySubs cx g lname term typ =
 
       let doms = extractDomains typ
           paramNames = extractParams term
@@ -276,12 +276,12 @@
                     Graph.graphPrimitives = (Graph.graphPrimitives g),
                     Graph.graphSchemaTypes = (Graph.graphSchemaTypes g),
                     Graph.graphTypeVariables = (Sets.union (Sets.fromList freeTypeVars) (Graph.graphTypeVariables g))}
-      in (Eithers.bind (Eithers.mapList (encodeTypedParam cx gWithTypeVars) zippedParams) (\sparams -> Eithers.bind (encodeTerm cx gWithTypeVars (extractBody term)) (\sbody -> Eithers.bind (encodeType cx g cod) (\scod ->
+      in (Eithers.bind (Eithers.mapList (encodeTypedParam cx gWithTypeVars) zippedParams) (\sparams -> Eithers.bind (encodeTerm overlaySubs cx gWithTypeVars (extractBody term)) (\sbody -> Eithers.bind (encodeType cx g cod) (\scod ->
         let gForLets =
                 Logic.ifElse (Lists.null letBindings) gWithTypeVars (Scoping.extendGraphForLet (\g2 -> \b -> Logic.ifElse (Predicates.isComplexBinding g2 b) (Just (Core.TermLiteral (Core.LiteralBoolean True))) Nothing) gWithTypeVars (Core.Let {
                   Core.letBindings = letBindings,
                   Core.letBody = (Core.TermVariable (Core.Name "dummy"))}))
-        in (Eithers.bind (Eithers.mapList (encodeLetBinding cx gForLets (Sets.fromList freeTypeVars)) letBindings) (\sbindings ->
+        in (Eithers.bind (Eithers.mapList (encodeLetBinding overlaySubs cx gForLets (Sets.fromList freeTypeVars)) letBindings) (\sbindings ->
           let defBody =
                   Logic.ifElse (Lists.null sbindings) sbody (Syntax.DataBlock (Syntax.BlockData {
                     Syntax.blockDataStats = (Lists.concat2 sbindings [
@@ -297,8 +297,8 @@
             Syntax.defDefnBody = defBody}))))))))))
 
 -- | Encode a Hydra function-valued term (lambda, project, cases, or unwrap) as a Scala expression
-encodeFunction :: t0 -> Graph.Graph -> M.Map Core.Name Core.Term -> Core.Term -> Maybe Core.Term -> Either Errors.Error Syntax.Data
-encodeFunction cx g meta funTerm arg =
+encodeFunction :: S.Set String -> t0 -> Graph.Graph -> M.Map Core.Name Core.Term -> Core.Term -> Maybe Core.Term -> Either Errors.Error Syntax.Data
+encodeFunction overlaySubs cx g meta funTerm arg =
     case (Strip.deannotateAndDetypeTerm funTerm) of
       Core.TermLambda v0 ->
         let param = Core.lambdaParameter v0
@@ -312,8 +312,8 @@
                                   Sets.fromList (Lists.filter (\n -> Logic.not (Lists.member 46 (Strings.toList (Core.unName n)))) (Sets.toList freeVars))
                           unresolvedVars = Sets.difference unqualifiedFreeVars (Graph.graphTypeVariables g)
                       in (Logic.ifElse (Sets.null unresolvedVars) (Just dom) Nothing))
-        in (Eithers.bind (encodeTerm cx g body) (\sbody -> Eithers.bind (Optionals.cases mdom (findSdom cx g meta) (\dom -> Eithers.bind (encodeType cx g dom) (\sdom -> Right (Just sdom)))) (\sdom -> Right (Utils.slambda v sbody sdom))))
-      Core.TermUnwrap _ -> Optionals.cases arg (Eithers.bind (findSdom cx g meta) (\sdom -> Right (Utils.slambda "x" (Utils.sname "x") sdom))) (\a -> encodeTerm cx g a)
+        in (Eithers.bind (encodeTerm overlaySubs cx g body) (\sbody -> Eithers.bind (Optionals.cases mdom (findSdom cx g meta) (\dom -> Eithers.bind (encodeType cx g dom) (\sdom -> Right (Just sdom)))) (\sdom -> Right (Utils.slambda v sbody sdom))))
+      Core.TermUnwrap _ -> Optionals.cases arg (Eithers.bind (findSdom cx g meta) (\sdom -> Right (Utils.slambda "x" (Utils.sname "x") sdom))) (\a -> encodeTerm overlaySubs cx g a)
       Core.TermProject v0 ->
         let fname = Utils.scalaEscapeName (Core.unName (Core.projectionFieldName v0))
             typeName = Core.projectionTypeName v0
@@ -321,7 +321,7 @@
         in (Optionals.cases arg (Eithers.bind (Eithers.either (\_ -> Right Nothing) (\msdom -> Right msdom) (findSdom cx g meta)) (\msdom -> Right (Utils.slambda pv (Syntax.DataRef (Syntax.RefDataSelect (Syntax.SelectData {
           Syntax.selectDataQual = (Utils.sname pv),
           Syntax.selectDataName = Syntax.NameData {
-            Syntax.nameDataValue = (Syntax.PredefString fname)}}))) msdom))) (\a -> Eithers.bind (encodeTerm cx g a) (\sa -> Right (Syntax.DataRef (Syntax.RefDataSelect (Syntax.SelectData {
+            Syntax.nameDataValue = (Syntax.PredefString fname)}}))) msdom))) (\a -> Eithers.bind (encodeTerm overlaySubs cx g a) (\sa -> Right (Syntax.DataRef (Syntax.RefDataSelect (Syntax.SelectData {
           Syntax.selectDataQual = sa,
           Syntax.selectDataName = Syntax.NameData {
             Syntax.nameDataValue = (Syntax.PredefString fname)}}))))))
@@ -333,20 +333,20 @@
             cases = Core.caseStatementCases v0
             dflt = Core.caseStatementDefault v0
             ftypes = Eithers.either (\_ -> Maps.empty) (\x_ -> x_) (Resolution.fieldTypes cx g dom)
-        in (Eithers.bind (Eithers.mapList (\f -> encodeCase cx g ftypes sn f) cases) (\fieldCases -> Eithers.bind (Optionals.cases dflt (Right fieldCases) (\dfltTerm -> Eithers.bind (encodeTerm cx g dfltTerm) (\sdflt -> Right (Lists.concat2 fieldCases [
+        in (Eithers.bind (Eithers.mapList (\f -> encodeCase overlaySubs cx g ftypes sn f) cases) (\fieldCases -> Eithers.bind (Optionals.cases dflt (Right fieldCases) (\dfltTerm -> Eithers.bind (encodeTerm overlaySubs cx g dfltTerm) (\sdflt -> Right (Lists.concat2 fieldCases [
           Syntax.Case {
             Syntax.casePat = Syntax.PatWildcard,
             Syntax.caseCond = Nothing,
             Syntax.caseBody = sdflt}])))) (\scases -> Optionals.cases arg (Eithers.bind (findSdom cx g meta) (\sdom -> Right (Utils.slambda v (Syntax.DataMatch (Syntax.MatchData {
           Syntax.matchDataExpr = (Utils.sname v),
-          Syntax.matchDataCases = scases})) sdom))) (\a -> Eithers.bind (encodeTerm cx g a) (\sa -> Right (Syntax.DataMatch (Syntax.MatchData {
+          Syntax.matchDataCases = scases})) sdom))) (\a -> Eithers.bind (encodeTerm overlaySubs cx g a) (\sa -> Right (Syntax.DataMatch (Syntax.MatchData {
           Syntax.matchDataExpr = sa,
           Syntax.matchDataCases = scases})))))))
       _ -> Left (Errors.ErrorOther (Errors.OtherError "unsupported function"))
 
 -- | Encode a let binding as a val or def declaration. outerTypeVars are type params from the enclosing scope.
-encodeLetBinding :: t0 -> Graph.Graph -> S.Set Core.Name -> Core.Binding -> Either Errors.Error Syntax.Stat
-encodeLetBinding cx g outerTypeVars b =
+encodeLetBinding :: S.Set String -> t0 -> Graph.Graph -> S.Set Core.Name -> Core.Binding -> Either Errors.Error Syntax.Stat
+encodeLetBinding overlaySubs cx g outerTypeVars b =
 
       let bname = Utils.scalaEscapeName (Core.unName (Core.bindingName b))
           bterm = Core.bindingTerm b
@@ -358,7 +358,7 @@
                       Core.TypeFunction _ -> True
                       _ -> False
                     _ -> False)
-      in (Optionals.cases mts (Eithers.bind (encodeTerm cx g bterm) (\srhs -> Right (Syntax.StatDefn (Syntax.DefnVal (Syntax.ValDefn {
+      in (Optionals.cases mts (Eithers.bind (encodeTerm overlaySubs cx g bterm) (\srhs -> Right (Syntax.StatDefn (Syntax.DefnVal (Syntax.ValDefn {
         Syntax.valDefnMods = [
           Syntax.ModLazy],
         Syntax.valDefnPats = [
@@ -369,7 +369,7 @@
         Syntax.valDefnRhs = srhs}))))) (\ts ->
         let newVars = Lists.filter (\v -> Logic.not (Sets.member v outerTypeVars)) (Core.typeSchemeVariables ts)
             useDef = Logic.or isFn (Logic.not (Lists.null newVars))
-        in (Logic.ifElse useDef (encodeLocalDef cx g outerTypeVars bname bterm (Core.typeSchemeBody ts)) (Eithers.bind (encodeTerm cx g bterm) (\srhs -> Eithers.bind (encodeType cx g (Core.typeSchemeBody ts)) (\styp -> Right (Syntax.StatDefn (Syntax.DefnVal (Syntax.ValDefn {
+        in (Logic.ifElse useDef (encodeLocalDef overlaySubs cx g outerTypeVars bname bterm (Core.typeSchemeBody ts)) (Eithers.bind (encodeTerm overlaySubs cx g bterm) (\srhs -> Eithers.bind (encodeType cx g (Core.typeSchemeBody ts)) (\styp -> Right (Syntax.StatDefn (Syntax.DefnVal (Syntax.ValDefn {
           Syntax.valDefnMods = [
             Syntax.ModLazy],
           Syntax.valDefnPats = [
@@ -405,8 +405,8 @@
       _ -> Left (Errors.ErrorOther (Errors.OtherError "unexpected literal"))
 
 -- | Encode a local def. outerTypeVars are type params already in scope (don't redeclare them).
-encodeLocalDef :: t0 -> Graph.Graph -> S.Set Core.Name -> String -> Core.Term -> Core.Type -> Either Errors.Error Syntax.Stat
-encodeLocalDef cx g outerTypeVars lname term typ =
+encodeLocalDef :: S.Set String -> t0 -> Graph.Graph -> S.Set Core.Name -> String -> Core.Term -> Core.Type -> Either Errors.Error Syntax.Stat
+encodeLocalDef overlaySubs cx g outerTypeVars lname term typ =
 
       let freeTypeVars =
               Lists.filter (\v -> Logic.and (Logic.not (Lists.member 46 (Strings.toList (Core.unName v)))) (Logic.not (Sets.member v outerTypeVars))) (Sets.toList (Variables.freeVariablesInType typ))
@@ -428,12 +428,12 @@
                     Graph.graphPrimitives = (Graph.graphPrimitives g),
                     Graph.graphSchemaTypes = (Graph.graphSchemaTypes g),
                     Graph.graphTypeVariables = (Sets.union allTypeVars (Graph.graphTypeVariables g))}
-      in (Eithers.bind (Eithers.mapList (encodeTypedParam cx gWithTypeVars) zippedParams) (\sparams -> Eithers.bind (encodeTerm cx gWithTypeVars (extractBody term)) (\sbody -> Eithers.bind (encodeType cx gWithTypeVars cod) (\scod ->
+      in (Eithers.bind (Eithers.mapList (encodeTypedParam cx gWithTypeVars) zippedParams) (\sparams -> Eithers.bind (encodeTerm overlaySubs cx gWithTypeVars (extractBody term)) (\sbody -> Eithers.bind (encodeType cx gWithTypeVars cod) (\scod ->
         let gForLets =
                 Logic.ifElse (Lists.null letBindings) gWithTypeVars (Scoping.extendGraphForLet (\g2 -> \b -> Logic.ifElse (Predicates.isComplexBinding g2 b) (Just (Core.TermLiteral (Core.LiteralBoolean True))) Nothing) gWithTypeVars (Core.Let {
                   Core.letBindings = letBindings,
                   Core.letBody = (Core.TermVariable (Core.Name "dummy"))}))
-        in (Eithers.bind (Eithers.mapList (encodeLetBinding cx gForLets allTypeVars) letBindings) (\sbindings ->
+        in (Eithers.bind (Eithers.mapList (encodeLetBinding overlaySubs cx gForLets allTypeVars) letBindings) (\sbindings ->
           let defBody =
                   Logic.ifElse (Lists.null sbindings) sbody (Syntax.DataBlock (Syntax.BlockData {
                     Syntax.blockDataStats = (Lists.concat2 sbindings [
@@ -449,8 +449,8 @@
             Syntax.defDefnBody = defBody}))))))))))
 
 -- | Encode a Hydra term as a Scala expression
-encodeTerm :: t0 -> Graph.Graph -> Core.Term -> Either Errors.Error Syntax.Data
-encodeTerm cx g term0 =
+encodeTerm :: S.Set String -> t0 -> Graph.Graph -> Core.Term -> Either Errors.Error Syntax.Data
+encodeTerm overlaySubs cx g term0 =
 
       let term = stripWrapEliminations term0
       in case (Strip.deannotateTerm term) of
@@ -472,12 +472,12 @@
               bodyAfterTypeLambdas = Pairs.second tlCollected
               substitutedBody = bodyAfterTypeLambdas
           in case (Strip.deannotateTerm substitutedBody) of
-            Core.TermProject _ -> encodeTerm cx g substitutedBody
-            Core.TermCases _ -> encodeTerm cx g substitutedBody
-            Core.TermUnwrap _ -> encodeTerm cx g substitutedBody
-            Core.TermVariable v1 -> Eithers.bind (correctTypeApps v1 typeArgs g) (\correctedTypeArgs -> Eithers.bind (Eithers.mapList (\targ -> encodeType cx g targ) correctedTypeArgs) (\stypeArgs -> Optionals.cases (Maps.lookup v1 (Graph.graphPrimitives g)) (Eithers.bind (encodeTerm cx g substitutedBody) (\svar -> Right (Utils.sapplyTypes svar stypeArgs))) (\_prim -> Right (Utils.sapplyTypes (Utils.sprim v1) stypeArgs))))
-            _ -> encodeTerm cx g substitutedBody
-        Core.TermTypeLambda v0 -> encodeTerm cx (Scoping.extendGraphForTypeLambda g v0) (Core.typeLambdaBody v0)
+            Core.TermProject _ -> encodeTerm overlaySubs cx g substitutedBody
+            Core.TermCases _ -> encodeTerm overlaySubs cx g substitutedBody
+            Core.TermUnwrap _ -> encodeTerm overlaySubs cx g substitutedBody
+            Core.TermVariable v1 -> Eithers.bind (correctTypeApps v1 typeArgs g) (\correctedTypeArgs -> Eithers.bind (Eithers.mapList (\targ -> encodeType cx g targ) correctedTypeArgs) (\stypeArgs -> Optionals.cases (Maps.lookup v1 (Graph.graphPrimitives g)) (Eithers.bind (encodeTerm overlaySubs cx g substitutedBody) (\svar -> Right (Utils.sapplyTypes svar stypeArgs))) (\_prim -> Right (Utils.sapplyTypes (Utils.sprim overlaySubs v1) stypeArgs))))
+            _ -> encodeTerm overlaySubs cx g substitutedBody
+        Core.TermTypeLambda v0 -> encodeTerm overlaySubs cx (Scoping.extendGraphForTypeLambda g v0) (Core.typeLambdaBody v0)
         Core.TermApplication v0 ->
           let fun = Core.applicationFunction v0
               arg = Core.applicationArgument v0
@@ -488,25 +488,25 @@
                 Core.TermApplication v2 ->
                   let innerFun = Core.applicationFunction v2
                   in case (Strip.deannotateAndDetypeTerm innerFun) of
-                    Core.TermCases _ -> encodeFunction cx g (Annotations.termAnnotationInternal innerFun) innerFun (Just arg)
-                    _ -> Eithers.bind (encodeTerm cx g fun) (\sfun -> Eithers.bind (encodeTerm cx g arg) (\sarg -> Right (Utils.sapply sfun [
+                    Core.TermCases _ -> encodeFunction overlaySubs cx g (Annotations.termAnnotationInternal innerFun) innerFun (Just arg)
+                    _ -> Eithers.bind (encodeTerm overlaySubs cx g fun) (\sfun -> Eithers.bind (encodeTerm overlaySubs cx g arg) (\sarg -> Right (Utils.sapply sfun [
                       sarg])))
-                _ -> Eithers.bind (encodeTerm cx g fun) (\sfun -> Eithers.bind (encodeTerm cx g arg) (\sarg -> Right (Utils.sapply sfun [
+                _ -> Eithers.bind (encodeTerm overlaySubs cx g fun) (\sfun -> Eithers.bind (encodeTerm overlaySubs cx g arg) (\sarg -> Right (Utils.sapply sfun [
                   sarg])))
             Core.TermProject v1 ->
               let fname = Utils.scalaEscapeName (Core.unName (Core.projectionFieldName v1))
-              in (Eithers.bind (encodeTerm cx g arg) (\sarg -> Right (Syntax.DataRef (Syntax.RefDataSelect (Syntax.SelectData {
+              in (Eithers.bind (encodeTerm overlaySubs cx g arg) (\sarg -> Right (Syntax.DataRef (Syntax.RefDataSelect (Syntax.SelectData {
                 Syntax.selectDataQual = sarg,
                 Syntax.selectDataName = Syntax.NameData {
                   Syntax.nameDataValue = (Syntax.PredefString fname)}})))))
-            Core.TermCases _ -> encodeFunction cx g (Annotations.termAnnotationInternal fun) fun (Just arg)
-            _ -> Eithers.bind (encodeTerm cx g fun) (\sfun -> Eithers.bind (encodeTerm cx g arg) (\sarg -> Right (Utils.sapply sfun [
+            Core.TermCases _ -> encodeFunction overlaySubs cx g (Annotations.termAnnotationInternal fun) fun (Just arg)
+            _ -> Eithers.bind (encodeTerm overlaySubs cx g fun) (\sfun -> Eithers.bind (encodeTerm overlaySubs cx g arg) (\sarg -> Right (Utils.sapply sfun [
               sarg])))
-        Core.TermLambda _ -> encodeFunction cx g (Annotations.termAnnotationInternal term) term Nothing
-        Core.TermProject _ -> encodeFunction cx g (Annotations.termAnnotationInternal term) term Nothing
-        Core.TermCases _ -> encodeFunction cx g (Annotations.termAnnotationInternal term) term Nothing
-        Core.TermUnwrap _ -> encodeFunction cx g (Annotations.termAnnotationInternal term) term Nothing
-        Core.TermList v0 -> Eithers.bind (Eithers.mapList (\e -> encodeTerm cx g e) v0) (\sels -> Right (Utils.sapply (Utils.sname "Seq") sels))
+        Core.TermLambda _ -> encodeFunction overlaySubs cx g (Annotations.termAnnotationInternal term) term Nothing
+        Core.TermProject _ -> encodeFunction overlaySubs cx g (Annotations.termAnnotationInternal term) term Nothing
+        Core.TermCases _ -> encodeFunction overlaySubs cx g (Annotations.termAnnotationInternal term) term Nothing
+        Core.TermUnwrap _ -> encodeFunction overlaySubs cx g (Annotations.termAnnotationInternal term) term Nothing
+        Core.TermList v0 -> Eithers.bind (Eithers.mapList (\e -> encodeTerm overlaySubs cx g e) v0) (\sels -> Right (Utils.sapply (Utils.sname "Seq") sels))
         Core.TermLiteral v0 -> Eithers.bind (encodeLiteral cx g v0) (\slit ->
           let litData = Syntax.DataLit slit
           in case v0 of
@@ -519,16 +519,16 @@
                 Syntax.DataLit (Syntax.LitString (Literals.showBigint (Literals.uint64ToBigint v2)))])
               _ -> Right litData
             _ -> Right litData)
-        Core.TermMap v0 -> Eithers.bind (Eithers.mapList (\kv -> Eithers.bind (encodeTerm cx g (Pairs.first kv)) (\sk -> Eithers.bind (encodeTerm cx g (Pairs.second kv)) (\sv -> Right (Utils.sassign sk sv)))) (Maps.toList v0)) (\spairs -> Right (Utils.sapply (Utils.sname "Map") spairs))
-        Core.TermWrap v0 -> encodeTerm cx g (Core.wrappedTermBody v0)
-        Core.TermOptional v0 -> Optionals.cases v0 (Right (Utils.sname "None")) (\t -> Eithers.bind (encodeTerm cx g t) (\s -> Right (Utils.sapply (Utils.sname "Some") [
+        Core.TermMap v0 -> Eithers.bind (Eithers.mapList (\kv -> Eithers.bind (encodeTerm overlaySubs cx g (Pairs.first kv)) (\sk -> Eithers.bind (encodeTerm overlaySubs cx g (Pairs.second kv)) (\sv -> Right (Utils.sassign sk sv)))) (Maps.toList v0)) (\spairs -> Right (Utils.sapply (Utils.sname "Map") spairs))
+        Core.TermWrap v0 -> encodeTerm overlaySubs cx g (Core.wrappedTermBody v0)
+        Core.TermOptional v0 -> Optionals.cases v0 (Right (Utils.sname "None")) (\t -> Eithers.bind (encodeTerm overlaySubs cx g t) (\s -> Right (Utils.sapply (Utils.sname "Some") [
           s])))
         Core.TermRecord v0 ->
           let rname = Core.recordTypeName v0
               fields = Core.recordFields v0
               n = Utils.scalaTypeName True rname
-          in (Eithers.bind (Eithers.mapList (\f -> encodeTerm cx g (Core.fieldTerm f)) fields) (\args -> Right (Utils.sapply (Utils.sname n) args)))
-        Core.TermSet v0 -> Eithers.bind (Eithers.mapList (\e -> encodeTerm cx g e) (Sets.toList v0)) (\sels -> Right (Utils.sapply (Utils.sname "scala.collection.immutable.Set") sels))
+          in (Eithers.bind (Eithers.mapList (\f -> encodeTerm overlaySubs cx g (Core.fieldTerm f)) fields) (\args -> Right (Utils.sapply (Utils.sname n) args)))
+        Core.TermSet v0 -> Eithers.bind (Eithers.mapList (\e -> encodeTerm overlaySubs cx g e) (Sets.toList v0)) (\sels -> Right (Utils.sapply (Utils.sname "scala.collection.immutable.Set") sels))
         Core.TermInject v0 ->
           let sn = Core.injectionTypeName v0
               fn = Core.fieldName (Core.injectionField v0)
@@ -541,9 +541,9 @@
             _ -> False) (\dom -> case (Strip.deannotateType dom) of
             Core.TypeUnit -> True
             Core.TypeRecord v1 -> Equality.equal (Lists.length v1) 0
-            _ -> False)) (Right lhs) (Eithers.bind (encodeTerm cx g ft) (\sarg -> Right (Utils.sapply lhs [
+            _ -> False)) (Right lhs) (Eithers.bind (encodeTerm overlaySubs cx g ft) (\sarg -> Right (Utils.sapply lhs [
             sarg]))))
-        Core.TermVariable v0 ->
+        Core.TermVariable v0 -> Optionals.cases (Maps.lookup v0 (Graph.graphPrimitives g)) (
           let fullName = Core.unName v0
               localName = Names.localNameOf v0
               parts = Strings.splitOn "." fullName
@@ -551,12 +551,12 @@
               escaped =
                       Logic.ifElse (Ordering.lte numParts 1) (Utils.scalaEscapeName fullName) (Logic.ifElse (Equality.equal numParts 2) (Strings.concat2 (Optionals.withDefault fullName (Lists.head parts)) (Strings.concat2 "." (Utils.scalaEscapeName localName))) (Strings.join "." (Lists.concat2 (Lists.take (Math.sub numParts 1) parts) [
                         Utils.scalaEscapeName localName])))
-          in (Right (Utils.sname escaped))
-        Core.TermAnnotated v0 -> encodeTerm cx g (Core.annotatedTermBody v0)
-        Core.TermEither v0 -> Eithers.either (\l -> Eithers.bind (encodeTerm cx g l) (\sl -> Right (Utils.sapply (Utils.sname "Left") [
-          sl]))) (\r -> Eithers.bind (encodeTerm cx g r) (\sr -> Right (Utils.sapply (Utils.sname "Right") [
+          in (Right (Utils.sname escaped))) (\_prim -> Right (Utils.sprim overlaySubs v0))
+        Core.TermAnnotated v0 -> encodeTerm overlaySubs cx g (Core.annotatedTermBody v0)
+        Core.TermEither v0 -> Eithers.either (\l -> Eithers.bind (encodeTerm overlaySubs cx g l) (\sl -> Right (Utils.sapply (Utils.sname "Left") [
+          sl]))) (\r -> Eithers.bind (encodeTerm overlaySubs cx g r) (\sr -> Right (Utils.sapply (Utils.sname "Right") [
           sr]))) v0
-        Core.TermPair v0 -> Eithers.bind (encodeTerm cx g (Pairs.first v0)) (\sf -> Eithers.bind (encodeTerm cx g (Pairs.second v0)) (\ss -> Right (Utils.sapply (Utils.sname "Tuple2") [
+        Core.TermPair v0 -> Eithers.bind (encodeTerm overlaySubs cx g (Pairs.first v0)) (\sf -> Eithers.bind (encodeTerm overlaySubs cx g (Pairs.second v0)) (\ss -> Right (Utils.sapply (Utils.sname "Tuple2") [
           sf,
           ss])))
         Core.TermUnit -> Right (Syntax.DataLit Syntax.LitUnit)
@@ -565,14 +565,14 @@
               body = Core.letBody v0
               gLet =
                       Scoping.extendGraphForLet (\g2 -> \b -> Logic.ifElse (Predicates.isComplexBinding g2 b) (Just (Core.TermLiteral (Core.LiteralBoolean True))) Nothing) g v0
-          in (Eithers.bind (Eithers.mapList (encodeLetBinding cx gLet (Graph.graphTypeVariables gLet)) bindings) (\sbindings -> Eithers.bind (encodeTerm cx gLet body) (\sbody -> Right (Syntax.DataBlock (Syntax.BlockData {
+          in (Eithers.bind (Eithers.mapList (encodeLetBinding overlaySubs cx gLet (Graph.graphTypeVariables gLet)) bindings) (\sbindings -> Eithers.bind (encodeTerm overlaySubs cx gLet body) (\sbody -> Right (Syntax.DataBlock (Syntax.BlockData {
             Syntax.blockDataStats = (Lists.concat2 sbindings [
               Syntax.StatTerm sbody])})))))
         _ -> Left (Errors.ErrorOther (Errors.OtherError "unexpected term"))
 
 -- | Encode a term definition as a Scala statement
-encodeTermDefinition :: t0 -> Graph.Graph -> Packaging.TermDefinition -> Either Errors.Error Syntax.Stat
-encodeTermDefinition cx g td =
+encodeTermDefinition :: S.Set String -> t0 -> Graph.Graph -> Packaging.TermDefinition -> Either Errors.Error Syntax.Stat
+encodeTermDefinition overlaySubs cx g td =
 
       let name = Packaging.termDefinitionName td
           term = Packaging.termDefinitionBody td
@@ -588,7 +588,7 @@
                     _ -> False
           freeTypeVarsInTyp =
                   Lists.filter (\v -> Logic.not (Lists.member 46 (Strings.toList (Core.unName v)))) (Sets.toList (Variables.freeVariablesInType typ_))
-      in (Logic.ifElse isFunctionType (encodeComplexTermDef cx g lname term typ_) (Logic.ifElse (Lists.null freeTypeVarsInTyp) (Eithers.bind (encodeType cx g typ_) (\stype -> Eithers.bind (encodeTerm cx g term) (\rhs -> Right (Syntax.StatDefn (Syntax.DefnVal (Syntax.ValDefn {
+      in (Logic.ifElse isFunctionType (encodeComplexTermDef overlaySubs cx g lname term typ_) (Logic.ifElse (Lists.null freeTypeVarsInTyp) (Eithers.bind (encodeType cx g typ_) (\stype -> Eithers.bind (encodeTerm overlaySubs cx g term) (\rhs -> Right (Syntax.StatDefn (Syntax.DefnVal (Syntax.ValDefn {
         Syntax.valDefnMods = [
           Syntax.ModLazy],
         Syntax.valDefnPats = [
@@ -596,7 +596,7 @@
             Syntax.varPatName = Syntax.NameData {
               Syntax.nameDataValue = (Syntax.PredefString lname)}})],
         Syntax.valDefnDecltpe = (Just stype),
-        Syntax.valDefnRhs = rhs})))))) (Eithers.bind (encodeType cx g typ_) (\stype -> Eithers.bind (encodeTerm cx g term) (\rhs ->
+        Syntax.valDefnRhs = rhs})))))) (Eithers.bind (encodeType cx g typ_) (\stype -> Eithers.bind (encodeTerm overlaySubs cx g term) (\rhs ->
         let tparams = Lists.map (\tv -> Utils.stparam tv) freeTypeVarsInTyp
         in (Right (Syntax.StatDefn (Syntax.DefnDef (Syntax.DefDefn {
           Syntax.defDefnMods = [],
@@ -854,9 +854,9 @@
         Syntax.paramDataDefault = Nothing})))
 
 -- | Encode an untyped application term by first inferring types
-encodeUntypeApplicationTerm :: Typing.InferenceContext -> Graph.Graph -> Core.Term -> Either Errors.Error Syntax.Data
-encodeUntypeApplicationTerm cx g term =
-    Eithers.bind (Inference.inferInGraphContext cx g term) (\result -> encodeTerm cx g (Typing.inferenceResultTerm result))
+encodeUntypeApplicationTerm :: S.Set String -> Typing.InferenceContext -> Graph.Graph -> Core.Term -> Either Errors.Error Syntax.Data
+encodeUntypeApplicationTerm overlaySubs cx g term =
+    Eithers.bind (Inference.inferInGraphContext cx g term) (\result -> encodeTerm overlaySubs cx g (Typing.inferenceResultTerm result))
 
 -- | Extract the innermost body from a term
 extractBody :: Core.Term -> Core.Term
@@ -1009,11 +1009,11 @@
       _ -> Left (Errors.ErrorOther (Errors.OtherError "expected a function type"))))
 
 -- | Find import statements for the module
-findImports :: t0 -> Graph.Graph -> Packaging.Module -> Either Errors.Error [Syntax.Stat]
-findImports cx g mod =
+findImports :: S.Set String -> t0 -> Graph.Graph -> Packaging.Module -> Either Errors.Error [Syntax.Stat]
+findImports overlaySubs cx g mod =
     Eithers.bind (Analysis.moduleDependencyModuleNames cx g False False True False mod) (\elImps -> Eithers.bind (Analysis.moduleDependencyModuleNames cx g False True False False mod) (\primImps -> Right (Lists.concat [
       Lists.map toElImport (Sets.toList elImps),
-      (Lists.map toPrimImport (Sets.toList primImps))])))
+      (Lists.map (toPrimImport overlaySubs) (Sets.toList primImps))])))
 
 -- | If t is Pair(var, _), return the first component's variable name (#589)
 findPairFirst :: Core.Type -> Maybe Core.Name
@@ -1058,9 +1058,9 @@
 isSimpleName name = Equality.equal (Lists.length (Strings.splitOn "." (Core.unName name))) 1
 
 -- | Convert a Hydra module to Scala source code
-moduleToScala :: Packaging.Module -> [Packaging.Definition] -> t0 -> Graph.Graph -> Either Errors.Error (M.Map String String)
-moduleToScala mod defs cx g =
-    Eithers.bind (constructModule cx g mod defs) (\pkg ->
+moduleToScala :: S.Set String -> Packaging.Module -> [Packaging.Definition] -> t0 -> Graph.Graph -> Either Errors.Error (M.Map String String)
+moduleToScala overlaySubs mod defs cx g =
+    Eithers.bind (constructModule overlaySubs cx g mod defs) (\pkg ->
       let s = Serialization.printExpr (Serialization.parenthesize (Serde.pkgToExpr pkg))
       in (Right (Maps.singleton (Names.moduleNameToFilePath Util.CaseConventionCamel (File.FileExtension "scala") (Packaging.moduleName mod)) s)))
 
@@ -1152,14 +1152,27 @@
             Syntax.ImporteeWildcard]}]}))
 
 -- | Create a primitive import statement
-toPrimImport :: Packaging.ModuleName -> Syntax.Stat
-toPrimImport ns =
-    Syntax.StatImportExport (Syntax.ImportExportStatImport (Syntax.Import {
-      Syntax.importImporters = [
-        Syntax.Importer {
-          Syntax.importerRef = (Syntax.RefDataName (Syntax.NameData {
-            Syntax.nameDataValue = (Syntax.PredefString (Strings.join "." (Strings.splitOn "." (Packaging.unModuleName ns))))})),
-          Syntax.importerImportees = []}]}))
+toPrimImport :: S.Set String -> Packaging.ModuleName -> Syntax.Stat
+toPrimImport overlaySubs ns =
+
+      let raw = Packaging.unModuleName ns
+          parts = Strings.splitOn "." raw
+          sub = Strings.join "." (Lists.drop 2 parts)
+          redirected =
+                  Logic.ifElse (Logic.and (Logic.and (Equality.equal (Lists.length parts) 3) (Equality.equal (Lists.take 2 parts) [
+                    "hydra",
+                    "lib"])) (Sets.member sub overlaySubs)) (Strings.concat2 "hydra.overlay.scala.lib." sub) raw
+          redirectedName =
+                  Syntax.NameData {
+                    Syntax.nameDataValue = (Syntax.PredefString (Strings.join "." (Strings.splitOn "." redirected)))}
+          redirectedRef = Syntax.RefDataName redirectedName
+          redirectedImporter =
+                  Syntax.Importer {
+                    Syntax.importerRef = redirectedRef,
+                    Syntax.importerImportees = []}
+      in (Syntax.StatImportExport (Syntax.ImportExportStatImport (Syntax.Import {
+        Syntax.importImporters = [
+          redirectedImporter]})))
 
 -- | Convert a type parameter to a type variable reference
 typeParamToTypeVar :: Syntax.ParamType -> Syntax.Type
diff --git a/src/main/haskell/Hydra/Scala/Utils.hs b/src/main/haskell/Hydra/Scala/Utils.hs
--- a/src/main/haskell/Hydra/Scala/Utils.hs
+++ b/src/main/haskell/Hydra/Scala/Utils.hs
@@ -152,11 +152,20 @@
 sname s = Syntax.DataRef (Syntax.RefDataName (Syntax.NameData {
   Syntax.nameDataValue = (Syntax.PredefString s)}))
 
--- | Create a Scala primitive reference from a Hydra name
-sprim :: Core.Name -> Syntax.Data
-sprim name =
+-- | Create a Scala primitive reference from a Hydra name, redirecting hydra.lib.<sub>.<local> to hydra.overlay.scala.lib.<sub>.<local> when <sub> has an overlay implementation on this host (#630 -- the on-disk overlaySubs existence signal). Unlike toPrimImport (which redirects a bare hydra.lib.<sub> MODULE name, always exactly 3 segments), a primitive reference is hydra.lib.<sub>.<local> -- at least 4 segments, since it also carries the primitive's own local name -- so the sub to check against overlaySubs is parts[2] alone, not the whole post-prefix tail (#635).
+sprim :: S.Set String -> Core.Name -> Syntax.Data
+sprim overlaySubs name =
 
-      let qname = Names.qualifyName name
+      let raw = Core.unName name
+          parts = Strings.splitOn "." raw
+          sub = Strings.join "." (Lists.drop 2 parts)
+          subHead = Optionals.withDefault "" (Lists.at 2 parts)
+          redirectedRaw =
+                  Logic.ifElse (Logic.and (Logic.and (Ordering.gte (Lists.length parts) 4) (Equality.equal (Lists.take 2 parts) [
+                    "hydra",
+                    "lib"])) (Sets.member subHead overlaySubs)) (Strings.concat2 "hydra.overlay.scala.lib." sub) raw
+          redirectedName = Core.Name redirectedRaw
+          qname = Names.qualifyName redirectedName
           prefix = Packaging.unModuleName (Optionals.withDefault (Packaging.ModuleName "") (Util.qualifiedNameModuleName qname))
           local = scalaEscapeName (Util.qualifiedNameLocal qname)
       in (sname (Strings.concat2 (Strings.concat2 prefix ".") local))
