keiro-core 0.8.0.0 → 0.9.0.0
raw patch · 3 files changed
+60/−3 lines, 3 filesdep ~keikiPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: keiki
API changes (from Hackage documentation)
+ Keiro.Codec.IdDomain: parseKindIdV7Text :: forall (prefix :: Symbol). ValidPrefix prefix => Text -> Either IdDomainFailure (KindID prefix)
+ Keiro.Codec.IdDomain: parseKindIdV7Value :: forall (prefix :: Symbol). ValidPrefix prefix => Value -> Parser (KindID prefix)
Files
- CHANGELOG.md +16/−0
- keiro-core.cabal +3/−2
- src/Keiro/Codec/IdDomain.hs +41/−1
CHANGELOG.md view
@@ -6,6 +6,22 @@ ## [Unreleased] +## 0.9.0.0 — 2026-08-02++### Breaking Changes++- Requires `keiki >=0.8 && <0.9`, replacing the previous `>=0.7 && <0.8` bound.+ Consumers must solve for the same Keiki major; verification results and+ rendering may differ from the 0.7 line.++### New Features++- Adds `parseKindIdV7Text` and `parseKindIdV7Value` to+ `Keiro.Codec.IdDomain`. Both construct a prefix-indexed `KindID` only after+ the frozen TypeID-v7 policy succeeds; the Aeson entry point preserves the+ owning JSON field path for malformed, wrong-prefix, non-canonical, and non-v7+ failures.+ ## 0.8.0.0 — 2026-08-01 No changes this release. Version moves with the package set.
keiro-core.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: keiro-core-version: 0.8.0.0+version: 0.9.0.0 synopsis: Core contracts for Keiro packages description: Stable stream, codec, event-stream, and integration-event contracts@@ -31,6 +31,7 @@ default-extensions: DeriveAnyClass DuplicateRecordFields+ ImportQualifiedPost MultilineStrings OverloadedLabels OverloadedStrings@@ -60,7 +61,7 @@ , bytestring >=0.11 && <0.13 , deepseq >=1.5 && <1.6 , generic-lens >=2.2 && <2.4- , keiki >=0.7 && <0.8+ , keiki >=0.8 && <0.9 , kiroku-store >=0.3 && <0.4 , lens >=5.2 && <5.4 , mmzk-typeid >=0.7 && <0.8
src/Keiro/Codec/IdDomain.hs view
@@ -7,15 +7,24 @@ typeIdV7Domain, idDomainAcceptsText, validateIdDomainText,+ parseKindIdV7Text,+ parseKindIdV7Value, idDomainTextPattern, idDomainSampleText, ) where +import Data.Aeson (Value, withText)+import Data.Aeson.Types (Parser)+import Data.KindID (KindID)+import Data.KindID qualified as KindID+import Data.KindID.Class (ValidPrefix) import Data.List.NonEmpty (NonEmpty (..))+import Data.Proxy (Proxy (..)) import Data.Text (Text) import Data.Text qualified as T import Data.TypeID qualified as TypeID+import GHC.TypeLits (symbolVal) import Keiki.ProjectionDomain ( DomainConstructionError, TextPattern,@@ -68,7 +77,13 @@ -- 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)+ parsed <- case TypeID.parseText input of+ Right value -> Right value+ Left reason ->+ case TypeID.parseText (T.toLower input) of+ Right canonical+ | TypeID.toText canonical == T.toLower input -> Left IdDomainNonCanonical+ _ -> Left (IdDomainMalformed (T.pack (show reason))) let actualPrefix = TypeID.getPrefix parsed if actualPrefix == idDomainPrefix contract then pure ()@@ -77,6 +92,31 @@ then pure () else Left IdDomainNonCanonical maybe (Right ()) (Left . IdDomainNotUuidV7 . T.pack . show) (TypeID.checkTypeID parsed)++-- | Parse a canonical TypeID-v7 whose prefix is reflected in the result type.+-- Keiro's frozen admission policy runs before the dependency constructs the+-- prefix-indexed value, so generated consumers cannot accidentally widen it.+parseKindIdV7Text :: forall prefix. (ValidPrefix prefix) => Text -> Either IdDomainFailure (KindID prefix)+parseKindIdV7Text input = do+ validateIdDomainText (typeIdV7Domain expectedPrefix) input+ either (Left . IdDomainMalformed . T.pack . show) Right (KindID.parseText @prefix input)+ where+ expectedPrefix = T.pack (symbolVal (Proxy @prefix))++-- | Aeson parser for generated integration-contract fields. When used with+-- @explicitParseField@, Aeson attaches the owning field key to these stable+-- Keiro admission failures.+parseKindIdV7Value :: forall prefix. (ValidPrefix prefix) => Value -> Parser (KindID prefix)+parseKindIdV7Value = withText "KindID" $ \input ->+ either (fail . T.unpack . renderIdDomainFailure) pure (parseKindIdV7Text @prefix input)++renderIdDomainFailure :: IdDomainFailure -> Text+renderIdDomainFailure failure = case failure of+ IdDomainNonCanonical -> "TypeID text is not canonical lowercase"+ IdDomainWrongPrefix expected actual ->+ "TypeID prefix mismatch: expected '" <> expected <> "', found '" <> actual <> "'"+ IdDomainMalformed reason -> "malformed TypeID text: " <> reason+ IdDomainNotUuidV7 reason -> "TypeID suffix is not UUIDv7: " <> reason idDomainTextPattern :: IdDomainContract -> Either DomainConstructionError TextPattern idDomainTextPattern contract = do