diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,28 @@
 
 ## [Unreleased]
 
+## 0.7.0.0 — 2026-08-01
+
+### Breaking Changes
+
+- Requires the conservative-projection Keiki release (`keiki >=0.7 && <0.8`),
+  replacing the previous `>=0.6 && <0.7` bound. Keiki 0.7 treats a predicate
+  that crosses a one-way generated projection as opaque to symbolic proof, so
+  verification may return `UnverifiedOpaque` where an earlier release reported a
+  `Verified*` result. Runtime codec and event-stream behavior is unchanged.
+
+### New Features
+
+- Adds the public `Keiro.Codec.IdDomain` module: the frozen
+  `keiro-dsl/id-domain/typeid-v7/1` runtime contract for prefix-bearing
+  TypeID-v7 identifiers. Exposes `IdDomainContract`, `IdDomainFailure`,
+  `IdNormalization`, `enforcedIdDomainVersion`, `typeIdV7Domain`,
+  `idDomainAcceptsText`, `validateIdDomainText`, `idDomainTextPattern`, and
+  `idDomainSampleText`. Admission requires canonical lowercase text, the
+  declared prefix and one underscore, a 26-character Crockford suffix, and
+  UUIDv7 version/variant bits; `idDomainTextPattern` yields the matching exact
+  Keiki projection domain. Adds a `mmzk-typeid >=0.7 && <0.8` dependency.
+
 ## 0.6.0.0 — 2026-07-31
 
 ### Breaking Changes
diff --git a/keiro-core.cabal b/keiro-core.cabal
--- a/keiro-core.cabal
+++ b/keiro-core.cabal
@@ -1,6 +1,6 @@
 cabal-version:   3.0
 name:            keiro-core
-version:         0.6.0.0
+version:         0.7.0.0
 synopsis:        Core contracts for Keiro packages
 description:
   Stable stream, codec, event-stream, and integration-event contracts
@@ -40,6 +40,7 @@
   import:          warnings, shared
   exposed-modules:
     Keiro.Codec
+    Keiro.Codec.IdDomain
     Keiro.Codec.Nominal
     Keiro.Codec.Structural
     Keiro.Codec.Structural.Generic
@@ -59,9 +60,10 @@
     , bytestring    >=0.11 && <0.13
     , deepseq       >=1.5  && <1.6
     , generic-lens  >=2.2  && <2.4
-    , keiki         >=0.6  && <0.7
+    , keiki         >=0.7  && <0.8
     , kiroku-store  >=0.3  && <0.4
     , lens          >=5.2  && <5.4
+    , mmzk-typeid   >=0.7  && <0.8
     , scientific    >=0.3  && <0.4
     , text          >=2.1  && <2.2
     , time          >=1.12 && <1.15
diff --git a/src/Keiro/Codec/IdDomain.hs b/src/Keiro/Codec/IdDomain.hs
new file mode 100644
--- /dev/null
+++ b/src/Keiro/Codec/IdDomain.hs
@@ -0,0 +1,112 @@
+-- | Published runtime contract for canonical prefix-bearing TypeID-v7 values.
+module Keiro.Codec.IdDomain
+  ( IdNormalization (..),
+    IdDomainContract (..),
+    IdDomainFailure (..),
+    enforcedIdDomainVersion,
+    typeIdV7Domain,
+    idDomainAcceptsText,
+    validateIdDomainText,
+    idDomainTextPattern,
+    idDomainSampleText,
+  )
+where
+
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.TypeID qualified as TypeID
+import Keiki.ProjectionDomain
+  ( DomainConstructionError,
+    TextPattern,
+    textCharSet,
+    textConcat,
+    textLiteral,
+    textRepeatBetween,
+  )
+
+data IdNormalization = CanonicalLowercase
+  deriving stock (Eq, Ord, Show)
+
+data IdDomainContract = IdDomainContract
+  { idDomainVersion :: !Text,
+    idDomainPrefix :: !Text,
+    idDomainSeparator :: !Char,
+    idDomainSuffixLength :: !Int,
+    idDomainMaxLength :: !Int,
+    idDomainNormalization :: !IdNormalization,
+    idDomainJsonRepresentation :: !Text
+  }
+  deriving stock (Eq, Ord, Show)
+
+data IdDomainFailure
+  = IdDomainNonCanonical
+  | IdDomainWrongPrefix !Text !Text
+  | IdDomainMalformed !Text
+  | IdDomainNotUuidV7 !Text
+  deriving stock (Eq, Ord, Show)
+
+enforcedIdDomainVersion :: Text
+enforcedIdDomainVersion = "keiro-dsl/id-domain/typeid-v7/1"
+
+typeIdV7Domain :: Text -> IdDomainContract
+typeIdV7Domain prefix =
+  IdDomainContract
+    { idDomainVersion = enforcedIdDomainVersion,
+      idDomainPrefix = prefix,
+      idDomainSeparator = '_',
+      idDomainSuffixLength = 26,
+      idDomainMaxLength = if T.null prefix then 26 else T.length prefix + 27,
+      idDomainNormalization = CanonicalLowercase,
+      idDomainJsonRepresentation = "canonical-json-text"
+    }
+
+idDomainAcceptsText :: IdDomainContract -> Text -> Bool
+idDomainAcceptsText contract = either (const False) (const True) . validateIdDomainText contract
+
+-- | @mmzk-typeid@ intentionally separates canonical parsing from the UUID
+-- version check, so both operations are part of this frozen contract.
+validateIdDomainText :: IdDomainContract -> Text -> Either IdDomainFailure ()
+validateIdDomainText contract input = do
+  parsed <- either (Left . IdDomainMalformed . T.pack . show) Right (TypeID.parseText input)
+  let actualPrefix = TypeID.getPrefix parsed
+  if actualPrefix == idDomainPrefix contract
+    then pure ()
+    else Left (IdDomainWrongPrefix (idDomainPrefix contract) actualPrefix)
+  if TypeID.toText parsed == input
+    then pure ()
+    else Left IdDomainNonCanonical
+  maybe (Right ()) (Left . IdDomainNotUuidV7 . T.pack . show) (TypeID.checkTypeID parsed)
+
+idDomainTextPattern :: IdDomainContract -> Either DomainConstructionError TextPattern
+idDomainTextPattern contract = do
+  prefix <-
+    textLiteral
+      ( if T.null (idDomainPrefix contract)
+          then ""
+          else idDomainPrefix contract <> T.singleton (idDomainSeparator contract)
+      )
+  leading <- textCharSet ('0' :| "1234567")
+  crockford <- textCharSet ('0' :| "123456789abcdefghjkmnpqrstvwxyz")
+  version <- textCharSet ('e' :| "f")
+  variant <- textCharSet ('8' :| "9abrstv")
+  beforeVersion <- textRepeatBetween 9 9 crockford
+  beforeVariant <- textRepeatBetween 2 2 crockford
+  afterVariant <- textRepeatBetween 12 12 crockford
+  pure
+    ( textConcat
+        ( prefix
+            :| [ leading,
+                 beforeVersion,
+                 version,
+                 beforeVariant,
+                 variant,
+                 afterVariant
+               ]
+        )
+    )
+
+idDomainSampleText :: IdDomainContract -> Text
+idDomainSampleText contract =
+  (if T.null (idDomainPrefix contract) then "" else idDomainPrefix contract <> "_")
+    <> "01h455vb4pex5vsknk084sn02q"
