diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,131 @@
 
 ---
 
+## [0.17.5] - 2026-08-19
+
+Point release on the 0.17.x line. Two language-surface changes lead: sum-type eliminators unify on
+`match` (scrutinee-first), and the primitive set is tentatively finalized — `strings.lines`/`unlines`
+are removed in favour of `hydra.formatting` term helpers. A symlink-aware filesystem family lands
+across all ten hosts, and Hydra's own build system continues its migration into Hydra.
+
+**Backward-incompatible.** The `cases` -> `match` rename and the `strings.lines`/`unlines` removal
+both change the published kernel surface. Code written against 0.17.4 that uses `Optionals.cases`
+or `strings.lines`/`strings.unlines` must be updated.
+
+### Highlights
+
+- **Sum-type eliminators unify on `match`** ([#615](https://github.com/CategoricalData/hydra/issues/615)):
+  the `cases`/`match` helper pair is flipped to a single scrutinee-first `match` convention across the
+  Haskell, Java, Python, and Scala DSLs, the `optionals.cases` -> `optionals.match` kernel primitive,
+  and the corresponding implementations in TypeScript and all four Lisp dialects.
+- **Primitive set tentatively finalized** ([#417](https://github.com/CategoricalData/hydra/issues/417)):
+  `strings.lines` and `strings.unlines` are removed as primitives; their semantics are preserved as
+  `hydra.formatting.lines`/`unlines` term helpers, with overlay implementations dropped across all ten
+  hosts and callers repointed.
+- **Symlink-aware filesystem primitives** ([#666](https://github.com/CategoricalData/hydra/issues/666)):
+  `status(followLinks)`, `createSymlink`, and `readSymlink` land with native implementations for every
+  host — Haskell, Java, Python, Scala, TypeScript, Clojure, Common Lisp, Scheme, and Emacs Lisp.
+- **Build system promotion continues** ([#416](https://github.com/CategoricalData/hydra/issues/416)):
+  adds `hydra.build.walk` extension-glob helpers (`extensionOf`/`filterByExtension`/`matchesExtension`)
+  and `hydra.build.comparereportlogic`, a set of pure snapshot-compare decision helpers.
+
+### Bug fixes
+
+- **`math.range` was inclusive of both bounds** ([#647](https://github.com/CategoricalData/hydra/issues/647)),
+  silently fabricating an extra element in index arithmetic.
+- **Java coder TCO-`cases` codegen** dropped enclosing-method type parameters from union-variant casts
+  (found via [#666](https://github.com/CategoricalData/hydra/issues/666)).
+- **`ShaclRdf` promoted to a DSL module** ([#652](https://github.com/CategoricalData/hydra/issues/652))
+  so the SHACL pipeline works outside Haskell.
+- **Cold-seed shim portability**: the R22 bootstrap shim used GNU-only `\+` and `\b`, which fail under
+  BSD `sed` on macOS ([#417](https://github.com/CategoricalData/hydra/issues/417)).
+- **Bootstrap Haskell target missing `unix`**: the demo's static `package.yaml` was not updated when
+  `System.Posix.Files` entered the kernel overlay, breaking every `*-to-haskell` bootstrap cell
+  ([#670](https://github.com/CategoricalData/hydra/issues/670)).
+- **Bootstrap comparison scored Scala and TypeScript cells as passing without comparing anything**:
+  `compare_output`'s target switch was missing both languages, so the baseline path was malformed,
+  every file counted as "no baseline found", and a zero-file comparison reported `pass`. A Scala or
+  TypeScript *target* cell could never fail ([#671](https://github.com/CategoricalData/hydra/issues/671)).
+
+### Known issues
+
+- A pre-#630 post-generation text pass rewrites `hydra.lib.<sub>` -> `hydra.overlay.<lang>.lib.<sub>`
+  in generated **doc comments** as well as code, because its quote-prefix guard does not recognize
+  mid-sentence prose references. Affects two kernel doc strings in `src/main`
+  (`hydra.error.system`, `hydra.error.packaging`) plus ~20 generated docstrings in the test tree;
+  cosmetic only, no behavioral impact. Tracked as part of
+  [#633](https://github.com/CategoricalData/hydra/issues/633).
+- Bootstrap comparison diff detail is not persisted to the run directory
+  ([#671](https://github.com/CategoricalData/hydra/issues/671)).
+
+---
+
+## [0.17.4] - 2026-08-09
+
+Point release on the 0.17.x line. Fixes an inference bug that mis-typed generated Java and Python for
+`readFile`, adds the `divide` primitive, and restores `hydra-ext` to the published package set. Release
+validation also surfaced a family of pre-existing cross-host code-generation defects — none of which the
+per-host CI jobs can catch, because only the bootstrap exercises one host generating *another*.
+
+### Highlights
+
+- **Output-only nominal generalization fixed** ([#637](https://github.com/CategoricalData/hydra/issues/637),
+  with [#638](https://github.com/CategoricalData/hydra/issues/638) /
+  [#639](https://github.com/CategoricalData/hydra/issues/639) /
+  [#640](https://github.com/CategoricalData/hydra/issues/640)): a nominal type appearing only in the
+  *output* position of a primitive's signature was generalized to a free type variable instead of resolving
+  as nominal, mis-typing the generated Java and Python for `readFile`. Also widens universe closure and adds
+  a `kernelTypeUniverse` helper, an effect DSL builder, and `effects`/`files`/`text`/`system` lib wrappers.
+- **`divide` primitive** ([#317](https://github.com/CategoricalData/hydra/issues/317)): now present in the
+  published kernel. This also clears the cold-clone dangle, since a cold seed against published 0.17.4
+  resolves `DefMath.divide`.
+- **`hydra-ext` republished** ([#636](https://github.com/CategoricalData/hydra/issues/636)): back in the
+  published set after its absence from 0.17.1–0.17.3, shipping to Hackage and PyPI.
+
+### Bug fixes
+
+- **Overlay-lib redirection skipped for the Haskell target**
+  ([#630](https://github.com/CategoricalData/hydra/issues/630)):
+  the Java, Python, Scheme, Clojure and Common Lisp heads resolved the overlay lib directory using an
+  all-lowercase path and did not lower-case the names they scanned. Haskell's overlay tree is capitalized
+  (`Hydra/Overlay/Haskell/Lib/`), so every `hydra.lib.*` redirect silently failed and the generated Haskell
+  imported `Hydra.Lib.*` instead of `Hydra.Overlay.Haskell.Lib.*`, producing hundreds of type errors. The
+  defect was invisible on case-sensitive filesystems, where the failed directory check falls back to a
+  hard-coded list that happens to be correct. The Python head carried *two* independent copies of this scan;
+  only the one in `generation.py` feeds the Haskell coder.
+- **Lisp-as-host bootstrap** ([#520](https://github.com/CategoricalData/hydra/issues/520),
+  [#630](https://github.com/CategoricalData/hydra/issues/630)): the four Lisp head decode drivers called
+  `from_json` with a stale 4-argument signature, and the Lisp bootstrap-driver coders were not pre-bound with
+  `overlaySubs`.
+- **Python-as-host dropped the `Graph` argument** ([#630](https://github.com/CategoricalData/hydra/issues/630)):
+  `overlaySubs` was not threaded into the Python head's `write_*` wrappers.
+- **Clojure `resolve-coder` and `decimal-to-float32`**
+  ([#630](https://github.com/CategoricalData/hydra/issues/630)): a stray parenthesis closed a `case` early,
+  making an unsupported-default block run unconditionally; and `(float (.doubleValue ...))` threw on
+  `Float.MAX_VALUE`, now saturating via `(.floatValue (bigdec x))`.
+- **Cold-seeder failed on macOS** ([#630](https://github.com/CategoricalData/hydra/issues/630)): the #376
+  cold seeder used a bare `sed -i -e`, which is GNU-only and aborts immediately under BSD `sed`.
+
+### Release engineering
+
+- **Review checkpoint before Maven Central publishes**
+  ([#591](https://github.com/CategoricalData/hydra/issues/591)): `HYDRA_JAVA_PUBLISH_HOLD=1` uploads the
+  aggregated Java deployment as `USER_MANAGED`, so it validates and then holds for review instead of
+  publishing automatically. This mirrors `HYDRA_SCALA_PUBLISH_HOLD` and makes an upload reversible — a held
+  deployment can be dropped, whereas an automatic one cannot be undone.
+
+### Known issues
+
+- **`hydra-ext` is not published to Maven Central**
+  ([#643](https://github.com/CategoricalData/hydra/issues/643)): it does not compile for the Java target,
+  because the visitor-pattern inner interface the Java coder emits collides with the enclosing `Visitor`
+  type in `hydra.cpp.syntax`. Only Java is affected; the Python coder emits sibling classes rather than
+  nested ones. ext ships to Hackage and PyPI at 0.17.4 and is expected to rejoin the Java set once fixed.
+- **TypeScript no longer self-hosts**
+  ([#642](https://github.com/CategoricalData/hydra/issues/642)): the TypeScript host's JSON decoder fails to
+  decode most of the kernel, so `typescript-as-host` code generation is broken. No published artifact is
+  affected — `dist/typescript` is generated by the Haskell host and passes its test suite.
+
 ## [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
diff --git a/hydra-rdf.cabal b/hydra-rdf.cabal
--- a/hydra-rdf.cabal
+++ b/hydra-rdf.cabal
@@ -1,11 +1,11 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.39.1.
+-- This file has been generated from package.yaml by hpack version 0.38.1.
 --
 -- see: https://github.com/sol/hpack
 
 name:           hydra-rdf
-version:        0.17.4
+version:        0.17.5
 synopsis:       Hydra's RDF/SHACL/OWL model and coder support
 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". RDF support for Hydra
 category:       Data
@@ -31,6 +31,7 @@
       Hydra.Dsl.Shacl.Model
       Hydra.Owl.Syntax
       Hydra.Rdf.Serde
+      Hydra.Rdf.ShaclRdf
       Hydra.Rdf.Syntax
       Hydra.Rdf.Utils
       Hydra.Shacl.Coder
@@ -45,6 +46,6 @@
   build-depends:
       base >=4.19.0 && <4.22
     , containers >=0.6.7 && <0.8
-    , hydra-kernel ==0.17.4
+    , hydra-kernel ==0.17.5
     , scientific >=0.3.7 && <0.4
   default-language: Haskell2010
diff --git a/src/main/haskell/Hydra/Rdf/Serde.hs b/src/main/haskell/Hydra/Rdf/Serde.hs
--- a/src/main/haskell/Hydra/Rdf/Serde.hs
+++ b/src/main/haskell/Hydra/Rdf/Serde.hs
@@ -109,7 +109,7 @@
                     (escapeLiteralString lex),
                     "\""])
           suffix =
-                  Optionals.cases lang (Serialization.noSep [
+                  Optionals.match lang (Serialization.noSep [
                     Serialization.cst "^^",
                     (iriToExpr dt)]) languageTagToExpr
       in (Serialization.noSep [
diff --git a/src/main/haskell/Hydra/Rdf/ShaclRdf.hs b/src/main/haskell/Hydra/Rdf/ShaclRdf.hs
new file mode 100644
--- /dev/null
+++ b/src/main/haskell/Hydra/Rdf/ShaclRdf.hs
@@ -0,0 +1,289 @@
+-- Note: this is an automatically generated file. Do not edit.
+
+-- | Serialize a SHACL ShapesGraph to RDF triples using the SHACL vocabulary
+
+module Hydra.Rdf.ShaclRdf where
+
+import qualified Hydra.Ast as Ast
+import qualified Hydra.Coders as Coders
+import qualified Hydra.Core as Core
+import qualified Hydra.Docs as Docs
+import qualified Hydra.Error.Checking as Checking
+import qualified Hydra.Error.Core as ErrorCore
+import qualified Hydra.Error.File as ErrorFile
+import qualified Hydra.Error.Packaging as ErrorPackaging
+import qualified Hydra.Error.System as ErrorSystem
+import qualified Hydra.Errors as Errors
+import qualified Hydra.File as File
+import qualified Hydra.Graph as Graph
+import qualified Hydra.Json.Model as JsonModel
+import qualified Hydra.Overlay.Haskell.Lib.Lists as Lists
+import qualified Hydra.Overlay.Haskell.Lib.Literals as Literals
+import qualified Hydra.Overlay.Haskell.Lib.Logic as Logic
+import qualified Hydra.Overlay.Haskell.Lib.Optionals as Optionals
+import qualified Hydra.Overlay.Haskell.Lib.Sets as Sets
+import qualified Hydra.Packaging as Packaging
+import qualified Hydra.Parsing as Parsing
+import qualified Hydra.Paths as Paths
+import qualified Hydra.Query as Query
+import qualified Hydra.Rdf.Serde as Serde
+import qualified Hydra.Rdf.Syntax as Syntax
+import qualified Hydra.Rdf.Utils as Utils
+import qualified Hydra.Regex as Regex
+import qualified Hydra.Relational as Relational
+import qualified Hydra.Shacl.Model as ShaclModel
+import qualified Hydra.System as System
+import qualified Hydra.Tabular as Tabular
+import qualified Hydra.Testing as Testing
+import qualified Hydra.Time as Time
+import qualified Hydra.Topology as Topology
+import qualified Hydra.Typed as Typed
+import qualified Hydra.Typing as Typing
+import qualified Hydra.Util as Util
+import qualified Hydra.Validation as Validation
+import qualified Hydra.Variants as Variants
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import qualified Data.Scientific as Sci
+
+-- | Create a boolean literal node
+booleanNode :: Bool -> Syntax.Node
+booleanNode b =
+    Syntax.NodeLiteral (Syntax.Literal {
+      Syntax.literalLexicalForm = (Logic.ifElse b "true" "false"),
+      Syntax.literalDatatypeIri = (xsd "boolean"),
+      Syntax.literalLanguageTag = Nothing})
+
+-- | Serialize a CommonConstraint to triples
+commonConstraintToTriples :: Syntax.Resource -> ShaclModel.CommonConstraint -> [Syntax.Triple]
+commonConstraintToTriples subj cc =
+    case cc of
+      ShaclModel.CommonConstraintDatatype v0 -> [
+        tripleRII subj (sh "datatype") v0]
+      ShaclModel.CommonConstraintClass _ -> []
+      ShaclModel.CommonConstraintNodeKind v0 -> [
+        tripleRII subj (sh "nodeKind") (nodeKindToIri v0)]
+      ShaclModel.CommonConstraintProperty v0 -> Lists.concat (Lists.map (propertyRefToTriples subj) (Sets.toList v0))
+      ShaclModel.CommonConstraintMaxInclusive v0 -> [
+        tripleRIN subj (sh "maxInclusive") (Syntax.NodeLiteral v0)]
+      ShaclModel.CommonConstraintMinInclusive v0 -> [
+        tripleRIN subj (sh "minInclusive") (Syntax.NodeLiteral v0)]
+      ShaclModel.CommonConstraintMaxExclusive v0 -> [
+        tripleRIN subj (sh "maxExclusive") (Syntax.NodeLiteral v0)]
+      ShaclModel.CommonConstraintMinExclusive v0 -> [
+        tripleRIN subj (sh "minExclusive") (Syntax.NodeLiteral v0)]
+      ShaclModel.CommonConstraintMaxLength v0 -> [
+        tripleRIN subj (sh "maxLength") (integerNode v0)]
+      ShaclModel.CommonConstraintMinLength v0 -> [
+        tripleRIN subj (sh "minLength") (integerNode v0)]
+      ShaclModel.CommonConstraintPattern v0 ->
+        let regex_ = ShaclModel.patternRegex v0
+            flags_ = ShaclModel.patternFlags v0
+        in (Lists.concat [
+          [
+            tripleRIN subj (sh "pattern") (stringNode regex_)],
+          (Optionals.match flags_ [] (\f -> [
+            tripleRIN subj (sh "flags") (stringNode f)]))])
+      ShaclModel.CommonConstraintClosed v0 -> [
+        tripleRIN subj (sh "closed") (booleanNode (ShaclModel.closedIsClosed v0))]
+      ShaclModel.CommonConstraintHasValue v0 -> Lists.map (\n -> tripleRIN subj (sh "hasValue") n) (Sets.toList v0)
+      ShaclModel.CommonConstraintIn v0 -> Lists.map (\n -> tripleRIN subj (sh "in") n) v0
+      ShaclModel.CommonConstraintNode v0 -> Lists.concat (Lists.map (\ref -> (\x -> case x of
+        ShaclModel.ReferenceNamed v1 -> [
+          tripleRII subj (sh "node") v1]
+        _ -> []) ref) (Sets.toList v0))
+      ShaclModel.CommonConstraintNot v0 -> Lists.concat (Lists.map (\ref -> (\x -> case x of
+        ShaclModel.ReferenceNamed v1 -> [
+          tripleRII subj (sh "not") v1]
+        _ -> []) ref) (Sets.toList v0))
+      ShaclModel.CommonConstraintAnd v0 -> Lists.concat (Lists.map (\ref -> (\x -> case x of
+        ShaclModel.ReferenceNamed v1 -> [
+          tripleRII subj (sh "and") v1]
+        _ -> []) ref) (Sets.toList v0))
+      ShaclModel.CommonConstraintOr v0 -> Lists.concat (Lists.map (\ref -> (\x -> case x of
+        ShaclModel.ReferenceNamed v1 -> [
+          tripleRII subj (sh "or") v1]
+        _ -> []) ref) (Sets.toList v0))
+      ShaclModel.CommonConstraintXone v0 -> Lists.concat (Lists.map (\ref -> (\x -> case x of
+        ShaclModel.ReferenceNamed v1 -> [
+          tripleRII subj (sh "xone") v1]
+        _ -> []) ref) (Sets.toList v0))
+      ShaclModel.CommonConstraintEquals _ -> []
+      ShaclModel.CommonConstraintDisjoint _ -> []
+      ShaclModel.CommonConstraintLanguageIn _ -> []
+
+-- | Serialize CommonProperties to triples
+commonPropertiesToTriples :: Syntax.Resource -> ShaclModel.CommonProperties -> [Syntax.Triple]
+commonPropertiesToTriples subj cp =
+    Lists.concat [
+      Lists.concat (Lists.map (commonConstraintToTriples subj) (Sets.toList (ShaclModel.commonPropertiesConstraints cp))),
+      (Optionals.match (ShaclModel.commonPropertiesDeactivated cp) [] (\b -> [
+        tripleRIN subj (sh "deactivated") (booleanNode b)])),
+      (Lists.concat (Lists.map (\n -> [
+        tripleRIN subj (sh "targetNode") (iriOrLiteralToNode n)]) (Sets.toList (ShaclModel.commonPropertiesTargetNode cp))))]
+
+-- | Serialize a Definition<Shape> to triples
+definitionToTriples :: ShaclModel.Definition ShaclModel.Shape -> [Syntax.Triple]
+definitionToTriples d =
+
+      let iri_ = ShaclModel.definitionIri d
+          shape = ShaclModel.definitionTarget d
+      in case shape of
+        ShaclModel.ShapeNode v0 -> nodeShapeToTriples iri_ v0
+        ShaclModel.ShapeProperty v0 -> propertyShapeToTriples (iriResource iri_) v0
+
+-- | Create an integer literal node
+integerNode :: Integer -> Syntax.Node
+integerNode n =
+    Syntax.NodeLiteral (Syntax.Literal {
+      Syntax.literalLexicalForm = (Literals.showBigint n),
+      Syntax.literalDatatypeIri = (xsd "integer"),
+      Syntax.literalLanguageTag = Nothing})
+
+-- | Create an IRI node
+iriNode :: Syntax.Iri -> Syntax.Node
+iriNode i = Syntax.NodeIri i
+
+-- | Convert an IriOrLiteral to an RDF Node
+iriOrLiteralToNode :: Syntax.IriOrLiteral -> Syntax.Node
+iriOrLiteralToNode il =
+    case il of
+      Syntax.IriOrLiteralIri v0 -> Syntax.NodeIri v0
+      Syntax.IriOrLiteralLiteral v0 -> Syntax.NodeLiteral v0
+
+-- | Create an IRI resource
+iriResource :: Syntax.Iri -> Syntax.Resource
+iriResource i = Syntax.ResourceIri i
+
+-- | Convert a NodeKind to its SHACL IRI
+nodeKindToIri :: ShaclModel.NodeKind -> Syntax.Iri
+nodeKindToIri nk =
+    case nk of
+      ShaclModel.NodeKindBlankNode -> sh "BlankNode"
+      ShaclModel.NodeKindIri -> sh "IRI"
+      ShaclModel.NodeKindLiteral -> sh "Literal"
+      ShaclModel.NodeKindBlankNodeOrIri -> sh "BlankNodeOrIRI"
+      ShaclModel.NodeKindBlankNodeOrLiteral -> sh "BlankNodeOrLiteral"
+      ShaclModel.NodeKindIriOrLiteral -> sh "IRIOrLiteral"
+
+-- | Serialize a NodeShape to triples
+nodeShapeToTriples :: Syntax.Iri -> ShaclModel.NodeShape -> [Syntax.Triple]
+nodeShapeToTriples iri_ ns_ =
+    Lists.concat [
+      [
+        tripleIII iri_ (rdf "type") (sh "NodeShape"),
+        (tripleIII iri_ (sh "targetClass") iri_)],
+      (commonPropertiesToTriples (iriResource iri_) (ShaclModel.nodeShapeCommon ns_))]
+
+-- | Serialize a property shape reference (may be inline or named)
+propertyRefToTriples :: Syntax.Resource -> ShaclModel.Reference ShaclModel.PropertyShape -> [Syntax.Triple]
+propertyRefToTriples subj ref =
+    (\x -> case x of
+      ShaclModel.ReferenceNamed v0 -> [
+        tripleRII subj (sh "property") v0]
+      ShaclModel.ReferenceDefinition v0 ->
+        let iri_ = ShaclModel.definitionIri v0
+            ps = ShaclModel.definitionTarget v0
+        in (Lists.concat [
+          [
+            tripleRII subj (sh "property") iri_],
+          (propertyShapeToTriples (iriResource iri_) ps)])
+      ShaclModel.ReferenceAnonymous _ -> []) ref
+
+-- | Serialize a PropertyShapeConstraint to triples
+propertyShapeConstraintToTriples :: Syntax.Resource -> ShaclModel.PropertyShapeConstraint -> [Syntax.Triple]
+propertyShapeConstraintToTriples subj psc =
+    case psc of
+      ShaclModel.PropertyShapeConstraintMinCount v0 -> [
+        tripleRIN subj (sh "minCount") (integerNode v0)]
+      ShaclModel.PropertyShapeConstraintMaxCount v0 -> [
+        tripleRIN subj (sh "maxCount") (integerNode v0)]
+      ShaclModel.PropertyShapeConstraintLessThan _ -> []
+      ShaclModel.PropertyShapeConstraintLessThanOrEquals _ -> []
+      ShaclModel.PropertyShapeConstraintUniqueLang v0 -> [
+        tripleRIN subj (sh "uniqueLang") (booleanNode v0)]
+      ShaclModel.PropertyShapeConstraintQualifiedValueShape _ -> []
+
+-- | Serialize a PropertyShape to triples, given a subject resource (may be blank node)
+propertyShapeToTriples :: Syntax.Resource -> ShaclModel.PropertyShape -> [Syntax.Triple]
+propertyShapeToTriples subj ps =
+    Lists.concat [
+      [
+        tripleRII subj (rdf "type") (sh "PropertyShape"),
+        (tripleRII subj (sh "path") (ShaclModel.propertyShapePath ps))],
+      (commonPropertiesToTriples subj (ShaclModel.propertyShapeCommon ps)),
+      (Lists.concat (Lists.map (propertyShapeConstraintToTriples subj) (Sets.toList (ShaclModel.propertyShapeConstraints ps)))),
+      (Optionals.match (ShaclModel.propertyShapeOrder ps) [] (\n -> [
+        tripleRIN subj (sh "order") (integerNode n)]))]
+
+-- | Construct an IRI in the RDF namespace
+rdf :: String -> Syntax.Iri
+rdf local = Utils.rdfIri local
+
+-- | Construct an IRI in the SHACL namespace
+sh :: String -> Syntax.Iri
+sh local = Utils.iri "http://www.w3.org/ns/shacl#" local
+
+-- | Convert a ShapesGraph to N-Triples string
+shapesGraphToNtriples :: ShaclModel.ShapesGraph -> String
+shapesGraphToNtriples sg =
+
+      let triples = shapesGraphToTriples sg
+          descs = Lists.map (\t -> tripleToDescription t) triples
+      in (Serde.rdfGraphToNtriples (Utils.descriptionsToGraph descs))
+
+-- | Convert a ShapesGraph to RDF triples
+shapesGraphToTriples :: ShaclModel.ShapesGraph -> [Syntax.Triple]
+shapesGraphToTriples sg = Lists.concat (Lists.map (\d -> definitionToTriples d) (Sets.toList (ShaclModel.unShapesGraph sg)))
+
+-- | Create a string literal node
+stringNode :: String -> Syntax.Node
+stringNode s =
+    Syntax.NodeLiteral (Syntax.Literal {
+      Syntax.literalLexicalForm = s,
+      Syntax.literalDatatypeIri = (xsd "string"),
+      Syntax.literalLanguageTag = Nothing})
+
+-- | Create a triple from IRI subject, IRI predicate, and IRI object
+tripleIII :: Syntax.Iri -> Syntax.Iri -> Syntax.Iri -> Syntax.Triple
+tripleIII s p o =
+    Syntax.Triple {
+      Syntax.tripleSubject = (iriResource s),
+      Syntax.triplePredicate = p,
+      Syntax.tripleObject = (iriNode o)}
+
+-- | Create a triple from IRI subject, IRI predicate, and node object
+tripleIIN :: Syntax.Iri -> Syntax.Iri -> Syntax.Node -> Syntax.Triple
+tripleIIN s p o =
+    Syntax.Triple {
+      Syntax.tripleSubject = (iriResource s),
+      Syntax.triplePredicate = p,
+      Syntax.tripleObject = o}
+
+-- | Create a triple from resource subject, IRI predicate, and IRI object
+tripleRII :: Syntax.Resource -> Syntax.Iri -> Syntax.Iri -> Syntax.Triple
+tripleRII s p o =
+    Syntax.Triple {
+      Syntax.tripleSubject = s,
+      Syntax.triplePredicate = p,
+      Syntax.tripleObject = (iriNode o)}
+
+-- | Create a triple from resource subject, IRI predicate, and node object
+tripleRIN :: Syntax.Resource -> Syntax.Iri -> Syntax.Node -> Syntax.Triple
+tripleRIN s p o =
+    Syntax.Triple {
+      Syntax.tripleSubject = s,
+      Syntax.triplePredicate = p,
+      Syntax.tripleObject = o}
+
+-- | Convert a single triple to a description (one triple per description)
+tripleToDescription :: Syntax.Triple -> Syntax.Description
+tripleToDescription t =
+
+      let subj = Syntax.tripleSubject t
+      in Syntax.Description {
+        Syntax.descriptionSubject = (Utils.resourceToNode subj),
+        Syntax.descriptionGraph = (Syntax.Graph (Sets.singleton t))}
+
+-- | Construct an IRI in the XML Schema namespace
+xsd :: String -> Syntax.Iri
+xsd local = Utils.xmlSchemaDatatypeIri local
diff --git a/src/main/haskell/Hydra/Rdf/Utils.hs b/src/main/haskell/Hydra/Rdf/Utils.hs
--- a/src/main/haskell/Hydra/Rdf/Utils.hs
+++ b/src/main/haskell/Hydra/Rdf/Utils.hs
@@ -167,7 +167,7 @@
           local_ = Util.qualifiedNameLocal qualName
       in (Syntax.Iri (Strings.concat [
         "urn:",
-        (Optionals.cases gname "" Packaging.unModuleName),
+        (Optionals.match gname "" Packaging.unModuleName),
         "#",
         (Formatting.decapitalize local_),
         (Formatting.capitalize (Core.unName fname))]))
diff --git a/src/main/haskell/Hydra/Shacl/Coder.hs b/src/main/haskell/Hydra/Shacl/Coder.hs
--- a/src/main/haskell/Hydra/Shacl/Coder.hs
+++ b/src/main/haskell/Hydra/Shacl/Coder.hs
@@ -43,6 +43,7 @@
 import qualified Hydra.Rdf.Utils as Utils
 import qualified Hydra.Regex as Regex
 import qualified Hydra.Relational as Relational
+import qualified Hydra.Rewriting as Rewriting
 import qualified Hydra.Shacl.Model as ShaclModel
 import qualified Hydra.Strip as Strip
 import qualified Hydra.System as System
@@ -132,7 +133,7 @@
         Syntax.Description {
           Syntax.descriptionSubject = (Syntax.NodeIri (Syntax.Iri "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil")),
           Syntax.descriptionGraph = (Syntax.Graph Sets.empty)}],
-      cx0)) (Optionals.cases (Lists.uncons terms) (Right ([], cx0)) (\p ->
+      cx0)) (Optionals.match (Lists.uncons terms) (Right ([], cx0)) (\p ->
       let pair1 = Utils.nextBlankNode cx0
           node1 = Pairs.first pair1
           cx1 = Pairs.second pair1
@@ -202,13 +203,9 @@
         in (Eithers.map (\_dr -> (
           Lists.concat2 (Utils.forObjects subject (Utils.keyIri _ks) (Utils.subjectsOf (Pairs.first _dr))) (Utils.triplesOf (Pairs.first _dr)),
           (Pairs.second _dr))) (encodeTerm node2 (Pairs.second kv) cx2 g)))) cx (Maps.toList v0))
-      Core.TermWrap v0 -> Eithers.map (\_dr ->
-        let descs = Pairs.first _dr
-            cx1 = Pairs.second _dr
-        in (
-          Optionals.withDefault descs (Optionals.map (\p -> Lists.cons (withType (Core.wrappedTermTypeName v0) (Pairs.first p)) (Pairs.second p)) (Lists.uncons descs)),
-          cx1)) (encodeTerm subject (Core.wrappedTermBody v0) cx g)
-      Core.TermOptional v0 -> Optionals.cases v0 (Right ([], cx)) (\_inner -> encodeTerm subject _inner cx g)
+      Core.TermWrap v0 -> encodeTerm subject (Rewriting.wrapTermToRecord (Core.wrappedTermTypeName v0) (Core.wrappedTermBody v0)) cx g
+      Core.TermOptional v0 -> Optionals.match v0 (Right ([], cx)) (\_inner -> encodeTerm subject _inner cx g)
+      Core.TermUnit -> Right ([], cx)
       Core.TermRecord v0 ->
         let rname = Core.recordTypeName v0
             fields = Core.recordFields v0
@@ -245,7 +242,7 @@
         Core.TypeLiteral v0 -> Right (encodeLiteralType v0)
         Core.TypeMap _ -> any
         Core.TypePair _ -> any
-        Core.TypeWrap _ -> any
+        Core.TypeWrap v0 -> encodeType tname (Rewriting.wrapTypeToRecord v0) cx
         Core.TypeRecord v0 -> Eithers.map (\_props -> common [
           ShaclModel.CommonConstraintProperty (Sets.fromList (Lists.map (\_p -> ShaclModel.ReferenceDefinition _p) _props))]) (Eithers.mapList (\_pair -> encodeFieldType tname (Just (Pairs.first _pair)) (Pairs.second _pair) cx) (Lists.zip (Lists.map (\_i -> Literals.int32ToBigint _i) (Math.range 0 (Lists.length v0))) v0))
         Core.TypeSet _ -> any
@@ -266,7 +263,7 @@
 -- | Fold over a list, accumulating results and threading context through each step
 foldAccumResult :: (t0 -> t1 -> Either t2 (t3, t0)) -> t0 -> [t1] -> Either t2 ([t3], t0)
 foldAccumResult f cx xs =
-    Optionals.cases (Lists.uncons xs) (Right ([], cx)) (\p -> Eithers.bind (f cx (Pairs.first p)) (\_r -> Eithers.map (\_rest -> (Lists.cons (Pairs.first _r) (Pairs.first _rest), (Pairs.second _rest))) (foldAccumResult f (Pairs.second _r) (Pairs.second p))))
+    Optionals.match (Lists.uncons xs) (Right ([], cx)) (\p -> Eithers.bind (f cx (Pairs.first p)) (\_r -> Eithers.map (\_rest -> (Lists.cons (Pairs.first _r) (Pairs.first _rest), (Pairs.second _rest))) (foldAccumResult f (Pairs.second _r) (Pairs.second p))))
 
 -- | Construct a SHACL node shape from a list of common constraints
 node :: [ShaclModel.CommonConstraint] -> ShaclModel.Shape
