shikumi-compile 0.2.0.3 → 0.2.1.0
raw patch · 9 files changed
+337/−9 lines, 9 filesdep ~baikaidep ~shikumidep ~shikumi-compilePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: baikai, shikumi, shikumi-compile
API changes (from Hackage documentation)
+ Shikumi.Compile.Structure: DuplicateRecipeId :: Text -> StructureRegistryError
+ Shikumi.Compile.Structure: EmptyRecipeId :: StructureRegistryError
+ Shikumi.Compile.Structure: EmptyRegistry :: StructureRegistryError
+ Shikumi.Compile.Structure: EmptyRegistryId :: StructureRegistryError
+ Shikumi.Compile.Structure: InvalidRecipeRevision :: Text -> Int -> StructureRegistryError
+ Shikumi.Compile.Structure: PopulatedBase :: StructureRegistryError
+ Shikumi.Compile.Structure: data RecipeId
+ Shikumi.Compile.Structure: data RecipeRevision
+ Shikumi.Compile.Structure: data RegistryId
+ Shikumi.Compile.Structure: data StructureRecipe i o
+ Shikumi.Compile.Structure: data StructureRegistry i o
+ Shikumi.Compile.Structure: data StructureRegistryError
+ Shikumi.Compile.Structure: directCotRegistry :: (ToSchema i, ToSchema o) => Text -> Program i o -> Either StructureRegistryError (StructureRegistry i o)
+ Shikumi.Compile.Structure: instance GHC.Classes.Eq Shikumi.Compile.Structure.RecipeId
+ Shikumi.Compile.Structure: instance GHC.Classes.Eq Shikumi.Compile.Structure.RecipeRevision
+ Shikumi.Compile.Structure: instance GHC.Classes.Eq Shikumi.Compile.Structure.RegistryId
+ Shikumi.Compile.Structure: instance GHC.Classes.Eq Shikumi.Compile.Structure.StructureRegistryError
+ Shikumi.Compile.Structure: instance GHC.Classes.Ord Shikumi.Compile.Structure.RecipeId
+ Shikumi.Compile.Structure: instance GHC.Classes.Ord Shikumi.Compile.Structure.RecipeRevision
+ Shikumi.Compile.Structure: instance GHC.Classes.Ord Shikumi.Compile.Structure.RegistryId
+ Shikumi.Compile.Structure: instance GHC.Internal.Show.Show Shikumi.Compile.Structure.RecipeId
+ Shikumi.Compile.Structure: instance GHC.Internal.Show.Show Shikumi.Compile.Structure.RecipeRevision
+ Shikumi.Compile.Structure: instance GHC.Internal.Show.Show Shikumi.Compile.Structure.RegistryId
+ Shikumi.Compile.Structure: instance GHC.Internal.Show.Show Shikumi.Compile.Structure.StructureRegistryError
+ Shikumi.Compile.Structure: lookupRecipe :: RecipeId -> StructureRegistry i o -> Maybe (StructureRecipe i o)
+ Shikumi.Compile.Structure: recipeDescription :: StructureRecipe i o -> Text
+ Shikumi.Compile.Structure: recipeId :: StructureRecipe i o -> RecipeId
+ Shikumi.Compile.Structure: recipeIdText :: RecipeId -> Text
+ Shikumi.Compile.Structure: recipeProgram :: StructureRecipe i o -> Program i o
+ Shikumi.Compile.Structure: recipeRevision :: StructureRecipe i o -> RecipeRevision
+ Shikumi.Compile.Structure: recipeRevisionNumber :: RecipeRevision -> Int
+ Shikumi.Compile.Structure: registryId :: StructureRegistry i o -> RegistryId
+ Shikumi.Compile.Structure: registryIdText :: RegistryId -> Text
+ Shikumi.Compile.Structure: registryInputSchema :: StructureRegistry i o -> Value
+ Shikumi.Compile.Structure: registryOutputSchema :: StructureRegistry i o -> Value
+ Shikumi.Compile.Structure: registryRecipes :: StructureRegistry i o -> NonEmpty (StructureRecipe i o)
+ Shikumi.Compile.Structure: structureRecipe :: Text -> Int -> Text -> Program i o -> Either StructureRegistryError (StructureRecipe i o)
+ Shikumi.Compile.Structure: structureRegistry :: (ToSchema i, ToSchema o) => Text -> [StructureRecipe i o] -> Either StructureRegistryError (StructureRegistry i o)
+ Shikumi.Compile.Structure.Serialize: InputSchemaMismatch :: Text -> StructureArtifactError
+ Shikumi.Compile.Structure.Serialize: MalformedStructureArtifact :: String -> StructureArtifactError
+ Shikumi.Compile.Structure.Serialize: OutputSchemaMismatch :: Text -> StructureArtifactError
+ Shikumi.Compile.Structure.Serialize: RegistryMismatch :: Text -> Text -> StructureArtifactError
+ Shikumi.Compile.Structure.Serialize: RevisionMismatch :: Text -> Int -> Int -> StructureArtifactError
+ Shikumi.Compile.Structure.Serialize: StructureArtifact :: !Text -> !Int -> !Text -> !Text -> !Int -> !Value -> !Value -> !ProgramShape -> ![Params] -> StructureArtifact
+ Shikumi.Compile.Structure.Serialize: StructureParameterCountMismatch :: Text -> Int -> Int -> StructureArtifactError
+ Shikumi.Compile.Structure.Serialize: StructureShapeMismatch :: Text -> StructureArtifactError
+ Shikumi.Compile.Structure.Serialize: UnknownRecipe :: Text -> StructureArtifactError
+ Shikumi.Compile.Structure.Serialize: UnsupportedStructureVersion :: Int -> StructureArtifactError
+ Shikumi.Compile.Structure.Serialize: WrongArtifactKind :: Text -> StructureArtifactError
+ Shikumi.Compile.Structure.Serialize: [artifactKind] :: StructureArtifact -> !Text
+ Shikumi.Compile.Structure.Serialize: [artifactRecipeId] :: StructureArtifact -> !Text
+ Shikumi.Compile.Structure.Serialize: [artifactRecipeRevision] :: StructureArtifact -> !Int
+ Shikumi.Compile.Structure.Serialize: [artifactRegistryId] :: StructureArtifact -> !Text
+ Shikumi.Compile.Structure.Serialize: [formatVersion] :: StructureArtifact -> !Int
+ Shikumi.Compile.Structure.Serialize: [inputSchema] :: StructureArtifact -> !Value
+ Shikumi.Compile.Structure.Serialize: [orderedParams] :: StructureArtifact -> ![Params]
+ Shikumi.Compile.Structure.Serialize: [outputSchema] :: StructureArtifact -> !Value
+ Shikumi.Compile.Structure.Serialize: [selectedShape] :: StructureArtifact -> !ProgramShape
+ Shikumi.Compile.Structure.Serialize: data StructureArtifact
+ Shikumi.Compile.Structure.Serialize: data StructureArtifactError
+ Shikumi.Compile.Structure.Serialize: decodeStructureArtifact :: StructureRegistry i o -> ByteString -> Either StructureArtifactError (CompiledProgram i o)
+ Shikumi.Compile.Structure.Serialize: encodeStructureArtifact :: StructureRegistry i o -> RecipeId -> CompiledProgram i o -> Either StructureArtifactError ByteString
+ Shikumi.Compile.Structure.Serialize: instance Data.Aeson.Types.FromJSON.FromJSON Shikumi.Compile.Structure.Serialize.StructureArtifact
+ Shikumi.Compile.Structure.Serialize: instance Data.Aeson.Types.ToJSON.ToJSON Shikumi.Compile.Structure.Serialize.StructureArtifact
+ Shikumi.Compile.Structure.Serialize: instance GHC.Classes.Eq Shikumi.Compile.Structure.Serialize.StructureArtifact
+ Shikumi.Compile.Structure.Serialize: instance GHC.Classes.Eq Shikumi.Compile.Structure.Serialize.StructureArtifactError
+ Shikumi.Compile.Structure.Serialize: instance GHC.Internal.Generics.Generic Shikumi.Compile.Structure.Serialize.StructureArtifact
+ Shikumi.Compile.Structure.Serialize: instance GHC.Internal.Show.Show Shikumi.Compile.Structure.Serialize.StructureArtifact
+ Shikumi.Compile.Structure.Serialize: instance GHC.Internal.Show.Show Shikumi.Compile.Structure.Serialize.StructureArtifactError
Files
- CHANGELOG.md +10/−0
- shikumi-compile.cabal +8/−5
- src/Shikumi/Compile.hs +6/−1
- src/Shikumi/Compile/ChainOfThought.hs +22/−2
- src/Shikumi/Compile/RAG.hs +4/−0
- src/Shikumi/Compile/Structure.hs +121/−0
- src/Shikumi/Compile/Structure/Serialize.hs +88/−0
- test/Main.hs +3/−1
- test/StructureSpec.hs +75/−0
CHANGELOG.md view
@@ -2,6 +2,16 @@ ## Unreleased +## 0.2.1.0 — 2026-09-08++- Raise the internal `shikumi` bound to `^>=0.4.0.0` for the breaking core release.++- Upgrade the dependency on `mori://shinzui/baikai/packages/baikai` to `>=0.7.0.0 && <0.8`.++- Add validated finite typed recipe registries and distinct version-1 experimental structure artifacts with exact registry/revision/schema/shape checks; existing compiled state is unchanged.++- Preserve capture codecs through parameter restoration and RAG; adapt codecs to reasoning/value outputs in the chain-of-thought rewrite. Saved parameter shapes remain unchanged.+ ## 0.2.0.3 — 2026-08-29 ### Changed
shikumi-compile.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.4 name: shikumi-compile-version: 0.2.0.3+version: 0.2.1.0 synopsis: The compiler layer for shikumi LM programs (EP-9) category: AI description:@@ -44,6 +44,8 @@ Shikumi.Compile.RAG Shikumi.Compile.Retriever Shikumi.Compile.Serialize+ Shikumi.Compile.Structure+ Shikumi.Compile.Structure.Serialize Shikumi.Compile.Types Shikumi.Compile.ZeroShot @@ -54,7 +56,7 @@ , effectful >=2.5 && <2.7 , generic-lens >=2.2 && <2.4 , lens ^>=5.3- , shikumi ^>=0.3.0.0+ , shikumi ^>=0.4.0.0 , text ^>=2.1 test-suite shikumi-compile-test@@ -64,19 +66,20 @@ main-is: Main.hs ghc-options: -threaded -with-rtsopts=-N other-modules:+ StructureSpec Test.Capture Test.Fixtures build-depends: , aeson- , baikai >=0.6 && <0.7+ , baikai >=0.7.0.0 && <0.8 , base , bytestring , effectful , generic-lens , lens- , shikumi ^>=0.3.0.0- , shikumi-compile ^>=0.2.0.0+ , shikumi ^>=0.4.0.0+ , shikumi-compile ^>=0.2.1.0 , tasty , tasty-hunit , text
src/Shikumi/Compile.hs view
@@ -19,7 +19,10 @@ -- This module re-exports the full public surface; see the per-strategy modules for -- detail and caveats. module Shikumi.Compile- ( -- * Types+ ( module Shikumi.Compile.Structure.Serialize,+ module Shikumi.Compile.Structure,++ -- * Types Compiler (..), CompiledProgram (..), compile,@@ -51,5 +54,7 @@ import Shikumi.Compile.RAG (formatPassages, rag) import Shikumi.Compile.Retriever (Passage (..), Retriever (..), inMemoryRetriever) import Shikumi.Compile.Serialize (decodeCompiledOnto, encodeCompiled)+import Shikumi.Compile.Structure+import Shikumi.Compile.Structure.Serialize import Shikumi.Compile.Types (CompiledProgram (..), Compiler (..), compile, identity, runCompiled) import Shikumi.Compile.ZeroShot (zeroShot, zeroShotClear)
src/Shikumi/Compile/ChainOfThought.hs view
@@ -41,10 +41,12 @@ ) where +import Data.Aeson (object, (.=)) import Shikumi.Compile.Types (Compiler (..))-import Shikumi.Module (chainOfThoughtRaw, value)+import Shikumi.Module (WithReasoning (..), chainOfThoughtRaw) import Shikumi.Program- ( Program+ ( CaptureCodec (..),+ Program ( Compose, Embed, Ensemble,@@ -53,12 +55,14 @@ Map, Parallel, Predict,+ PredictCaptured, Retry, RetryWhen, Validate ), mapParams, )+import Shikumi.Schema.Types (objectSchema, stringSchema) -- | Rewrite every 'Predict' node into its chain-of-thought form, recursing through -- every composite/combinator node so nodes nested arbitrarily deep are reached.@@ -72,6 +76,22 @@ -- ('Shikumi.Schema.FromModel' @i@/@o@, 'Shikumi.Schema.ToSchema' @o@, the -- 'Shikumi.Adapter.ToPrompt's) are exactly what 'chainOfThoughtRaw' needs. cot :: Program i o -> Program i o+cot (PredictCaptured codec sig ps) =+ case chainOfThoughtRaw sig of+ Predict augmented _ ->+ FMap+ value+ ( PredictCaptured+ ( CaptureCodec+ (encodeCaptureInput codec)+ (\o -> object ["reasoning" .= reasoning o, "value" .= encodeCaptureOutput codec (value o)])+ (captureInputSchema codec)+ (objectSchema [("reasoning", stringSchema), ("value", captureOutputSchema codec)] ["reasoning", "value"])+ )+ augmented+ ps+ )+ other -> FMap value other cot (Predict sig ps) = FMap value (mapParams (const ps) (chainOfThoughtRaw sig)) cot (Compose a b) = Compose (cot a) (cot b) cot (FMap k p) = FMap k (cot p)
src/Shikumi/Compile/RAG.hs view
@@ -48,6 +48,7 @@ Map, Parallel, Predict,+ PredictCaptured, Retry, RetryWhen, Validate@@ -80,6 +81,9 @@ | otherwise = go where go :: forall i o. Program i o -> Program i o+ go (PredictCaptured codec sig ps) =+ let base = fromMaybe (getInstruction sig) (instructionOverride ps)+ in PredictCaptured codec sig ps {instructionOverride = Just (base <> "\n\n" <> ctx)} go (Predict sig ps) = let base = fromMaybe (getInstruction sig) (instructionOverride ps) in Predict sig ps {instructionOverride = Just (base <> "\n\n" <> ctx)}
+ src/Shikumi/Compile/Structure.hs view
@@ -0,0 +1,121 @@+-- | Finite, trusted typed implementations. Identity is a caller-owned contract:+-- advance revisions whenever signatures, reducers or opaque code change.+module Shikumi.Compile.Structure+ ( RecipeId,+ recipeIdText,+ RecipeRevision,+ recipeRevisionNumber,+ RegistryId,+ registryIdText,+ StructureRecipe,+ recipeId,+ recipeRevision,+ recipeDescription,+ recipeProgram,+ StructureRegistry,+ registryId,+ registryRecipes,+ registryInputSchema,+ registryOutputSchema,+ StructureRegistryError (..),+ structureRecipe,+ structureRegistry,+ lookupRecipe,+ directCotRegistry,+ )+where++import Control.Monad (unless)+import Data.Aeson (Value)+import Data.List (find, nub)+import Data.List.NonEmpty (NonEmpty (..))+import Data.List.NonEmpty qualified as NE+import Data.Proxy (Proxy (..))+import Data.Text (Text)+import Data.Text qualified as T+import Shikumi.Compile.ChainOfThought (chainOfThoughtCompiler)+import Shikumi.Compile.Types (Compiler (..))+import Shikumi.Program (Program, emptyParams, programParams)+import Shikumi.Schema (ToSchema (..))++newtype RecipeId = RecipeId Text deriving stock (Eq, Ord, Show)++newtype RecipeRevision = RecipeRevision Int deriving stock (Eq, Ord, Show)++newtype RegistryId = RegistryId Text deriving stock (Eq, Ord, Show)++recipeIdText :: RecipeId -> Text+recipeIdText (RecipeId ident) = ident++recipeRevisionNumber :: RecipeRevision -> Int+recipeRevisionNumber (RecipeRevision revision) = revision++registryIdText :: RegistryId -> Text+registryIdText (RegistryId ident) = ident++-- Constructors and record labels are private, so record updates cannot bypass+-- registry validation or replace its typed schema evidence.+data StructureRecipe i o = StructureRecipe RecipeId RecipeRevision Text (Program i o)++data StructureRegistry i o = StructureRegistry RegistryId Value Value (NonEmpty (StructureRecipe i o))++recipeId :: StructureRecipe i o -> RecipeId+recipeId (StructureRecipe x _ _ _) = x++recipeRevision :: StructureRecipe i o -> RecipeRevision+recipeRevision (StructureRecipe _ x _ _) = x++recipeDescription :: StructureRecipe i o -> Text+recipeDescription (StructureRecipe _ _ x _) = x++recipeProgram :: StructureRecipe i o -> Program i o+recipeProgram (StructureRecipe _ _ _ x) = x++registryId :: StructureRegistry i o -> RegistryId+registryId (StructureRegistry x _ _ _) = x++registryInputSchema :: StructureRegistry i o -> Value+registryInputSchema (StructureRegistry _ x _ _) = x++registryOutputSchema :: StructureRegistry i o -> Value+registryOutputSchema (StructureRegistry _ _ x _) = x++registryRecipes :: StructureRegistry i o -> NonEmpty (StructureRecipe i o)+registryRecipes (StructureRegistry _ _ _ x) = x++data StructureRegistryError+ = EmptyRecipeId+ | InvalidRecipeRevision Text Int+ | EmptyRegistryId+ | EmptyRegistry+ | DuplicateRecipeId Text+ | PopulatedBase+ deriving stock (Eq, Show)++structureRecipe :: Text -> Int -> Text -> Program i o -> Either StructureRegistryError (StructureRecipe i o)+structureRecipe ident revision description program = do+ unless (not (T.null (T.strip ident))) (Left EmptyRecipeId)+ unless (revision > 0) (Left (InvalidRecipeRevision ident revision))+ pure (StructureRecipe (RecipeId ident) (RecipeRevision revision) description program)++structureRegistry :: forall i o. (ToSchema i, ToSchema o) => Text -> [StructureRecipe i o] -> Either StructureRegistryError (StructureRegistry i o)+structureRegistry ident recipes = do+ unless (not (T.null (T.strip ident))) (Left EmptyRegistryId)+ rs <- maybe (Left EmptyRegistry) Right (NE.nonEmpty recipes)+ let ids = map recipeId recipes+ case find (\x -> length (filter (== x) ids) > 1) (nub ids) of+ Just duplicate -> Left (DuplicateRecipeId (recipeIdText duplicate))+ Nothing -> pure (StructureRegistry (RegistryId ident) (toSchema (Proxy @i)) (toSchema (Proxy @o)) rs)++lookupRecipe :: RecipeId -> StructureRegistry i o -> Maybe (StructureRecipe i o)+lookupRecipe ident = find ((== ident) . recipeId) . registryRecipes++-- | Register @direct@ then @cot@, both revision 1. Reject any populated+-- parameters; explicitly use @mapParams (const emptyParams)@ first if desired.+-- Independently optimized variants should use 'structureRecipe' instead.+directCotRegistry :: (ToSchema i, ToSchema o) => Text -> Program i o -> Either StructureRegistryError (StructureRegistry i o)+directCotRegistry ident base = do+ unless (all (== emptyParams) (programParams base)) (Left PopulatedBase)+ direct <- structureRecipe "direct" 1 "Direct base program" base+ cot <- structureRecipe "cot" 1 "Reasoning then projection" (runCompiler chainOfThoughtCompiler base)+ structureRegistry ident [direct, cot]
+ src/Shikumi/Compile/Structure/Serialize.hs view
@@ -0,0 +1,88 @@+-- | Versioned experimental artifacts, restored only through trusted caller code.+-- Schema/shape equality detects declared incompatibility; it does not attest to+-- opaque function identity. Owners must advance revisions for behavioral changes.+module Shikumi.Compile.Structure.Serialize+ ( StructureArtifact (..),+ StructureArtifactError (..),+ encodeStructureArtifact,+ decodeStructureArtifact,+ )+where++import Control.Monad (unless)+import Data.Aeson (FromJSON, ToJSON, Value, eitherDecode, encode)+import Data.ByteString.Lazy (ByteString)+import Data.List (find)+import Data.Text (Text)+import GHC.Generics (Generic)+import Shikumi.Compile.Structure+import Shikumi.Compile.Types (CompiledProgram (..))+import Shikumi.Program (Params, ProgramShape, programParams, programShape, setProgramParams)++data StructureArtifact = StructureArtifact+ { artifactKind :: !Text,+ formatVersion :: !Int,+ artifactRegistryId :: !Text,+ artifactRecipeId :: !Text,+ artifactRecipeRevision :: !Int,+ inputSchema :: !Value,+ outputSchema :: !Value,+ selectedShape :: !ProgramShape,+ orderedParams :: ![Params]+ }+ deriving stock (Eq, Show, Generic)+ deriving anyclass (ToJSON, FromJSON)++data StructureArtifactError+ = MalformedStructureArtifact String+ | WrongArtifactKind Text+ | UnsupportedStructureVersion Int+ | RegistryMismatch Text Text+ | UnknownRecipe Text+ | RevisionMismatch Text Int Int+ | InputSchemaMismatch Text+ | OutputSchemaMismatch Text+ | StructureShapeMismatch Text+ | StructureParameterCountMismatch Text Int Int+ deriving stock (Eq, Show)++encodeStructureArtifact :: StructureRegistry i o -> RecipeId -> CompiledProgram i o -> Either StructureArtifactError ByteString+encodeStructureArtifact registry ident (CompiledProgram program) = do+ recipe <- maybe (Left (UnknownRecipe (recipeIdText ident))) Right (lookupRecipe ident registry)+ let artifact =+ StructureArtifact+ "shikumi.experimental.structure"+ 1+ (registryIdText (registryId registry))+ (recipeIdText ident)+ (recipeRevisionNumber (recipeRevision recipe))+ (registryInputSchema registry)+ (registryOutputSchema registry)+ (programShape program)+ (programParams program)+ _ <- restore registry artifact+ pure (encode artifact)++decodeStructureArtifact :: StructureRegistry i o -> ByteString -> Either StructureArtifactError (CompiledProgram i o)+decodeStructureArtifact registry bytes = do+ artifact <- either (Left . MalformedStructureArtifact) Right (eitherDecode bytes)+ restore registry artifact++restore :: StructureRegistry i o -> StructureArtifact -> Either StructureArtifactError (CompiledProgram i o)+restore registry artifact = do+ unless (artifactKind artifact == "shikumi.experimental.structure") (Left (WrongArtifactKind (artifactKind artifact)))+ unless (formatVersion artifact == 1) (Left (UnsupportedStructureVersion (formatVersion artifact)))+ let expectedRegistry = registryIdText (registryId registry)+ ident = artifactRecipeId artifact+ unless (artifactRegistryId artifact == expectedRegistry) (Left (RegistryMismatch expectedRegistry (artifactRegistryId artifact)))+ recipe <- maybe (Left (UnknownRecipe ident)) Right (find ((== ident) . recipeIdText . recipeId) (registryRecipes registry))+ let revision = recipeRevisionNumber (recipeRevision recipe)+ template = recipeProgram recipe+ count = length (programParams template)+ actual = length (orderedParams artifact)+ unless (artifactRecipeRevision artifact == revision) (Left (RevisionMismatch ident revision (artifactRecipeRevision artifact)))+ unless (inputSchema artifact == registryInputSchema registry) (Left (InputSchemaMismatch ident))+ unless (outputSchema artifact == registryOutputSchema registry) (Left (OutputSchemaMismatch ident))+ unless (selectedShape artifact == programShape template) (Left (StructureShapeMismatch ident))+ unless (actual == count) (Left (StructureParameterCountMismatch ident count actual))+ either (const (Left (StructureParameterCountMismatch ident count actual))) (Right . CompiledProgram) (setProgramParams (orderedParams artifact) template)
test/Main.hs view
@@ -22,6 +22,7 @@ ) import Shikumi.Compile.Retriever (Passage (..)) import Shikumi.Program (Params (..), foldParams)+import StructureSpec qualified import Test.Capture (runWithCapture) import Test.Fixtures ( Question (..),@@ -43,7 +44,8 @@ tests = testGroup "shikumi-compile (EP-9)"- [ m0_identity,+ [ StructureSpec.tests,+ m0_identity, m1_baseline, m2_zeroShot, m3_fewShot,
+ test/StructureSpec.hs view
@@ -0,0 +1,75 @@+module StructureSpec (tests) where++import Data.Aeson (Value (Null), eitherDecode, encode)+import Data.Either (isLeft)+import Data.List.NonEmpty qualified as NE+import Shikumi.Compile+import Shikumi.Program (programShape)+import Test.Capture (runWithCapture)+import Test.Fixtures+import Test.Tasty+import Test.Tasty.HUnit++right :: (Show e) => Either e a -> IO a+right = either (fail . show) pure++tests :: TestTree+tests =+ testGroup+ "typed structures"+ [ testCase "invalid metadata rejected" $ do+ assertBool "empty ID" (isLeft (structureRecipe " " 1 "" qaBase))+ assertBool "revision" (isLeft (structureRecipe "a" 0 "" qaBase))+ a <- right (structureRecipe "a" 1 "" qaBase)+ assertBool "empty registry" (isLeft (structureRegistry "r" ([] :: [StructureRecipe Question Answer])))+ assertBool "registry ID" (isLeft (structureRegistry "" [a]))+ assertBool "duplicate" (isLeft (structureRegistry "r" [a, a])),+ testCase "direct/CoT order and distinct shapes" $ do+ r <- right (directCotRegistry "r" qaBase)+ let rs = NE.toList (registryRecipes r)+ map (recipeIdText . recipeId) rs @?= ["direct", "cot"]+ case rs of+ [direct, cot] -> assertBool "different shapes" (programShape (recipeProgram direct) /= programShape (recipeProgram cot))+ _ -> assertFailure "expected direct and cot",+ testCase "populated helper base rejected; explicit pipelines accepted" $ do+ assertBool "no silent deletion" (isLeft (directCotRegistry "r" (compiledProgram (compile (fewShotTyped demoPairs) qaBase))))+ a <- right (structureRecipe "direct" 1 "" qaBase)+ b <- right (structureRecipe "pipeline" 1 "" qaPipeline)+ r <- right (structureRegistry "r" [a, b])+ length (registryRecipes r) @?= 2,+ testCase "artifact restores compatible demos and requests" $ do+ r <- right (directCotRegistry "r" qaBase)+ let ident = recipeId (NE.head (registryRecipes r))+ original = compile (fewShotTyped demoPairs) qaBase+ bytes <- right (encodeStructureArtifact r ident original)+ restored <- right (decodeStructureArtifact r bytes)+ before <- runWithCapture [answerResponse] (compiledProgram original) (Question "test")+ restoredCapture <- runWithCapture [answerResponse] (compiledProgram restored) (Question "test")+ restoredCapture @?= before,+ testCase "artifact incompatibilities are pure categorized failures" $ do+ r <- right (directCotRegistry "r" qaBase)+ let direct = NE.head (registryRecipes r)+ ident = recipeId direct+ bytes <- right (encodeStructureArtifact r ident (compile identity qaBase))+ artifact <- right (eitherDecode bytes)+ let check change expected = case decodeStructureArtifact r (encode (change artifact)) of+ Left err -> err @?= expected+ Right _ -> assertFailure "unexpected compatible artifact"+ check (\a -> a {formatVersion = 2}) (UnsupportedStructureVersion 2)+ check (\a -> a {artifactKind = "production"}) (WrongArtifactKind "production")+ check (\a -> a {artifactRegistryId = "other"}) (RegistryMismatch "r" "other")+ check (\a -> a {artifactRecipeId = "missing"}) (UnknownRecipe "missing")+ check (\a -> a {artifactRecipeRevision = 2}) (RevisionMismatch "direct" 1 2)+ check (\a -> a {inputSchema = Null}) (InputSchemaMismatch "direct")+ check (\a -> a {outputSchema = Null}) (OutputSchemaMismatch "direct")+ check (\a -> a {selectedShape = programShape qaPipeline}) (StructureShapeMismatch "direct")+ check (\a -> a {orderedParams = []}) (StructureParameterCountMismatch "direct" 1 0)+ check (\a -> a {orderedParams = orderedParams a ++ orderedParams a}) (StructureParameterCountMismatch "direct" 1 2)+ assertBool "malformed" (isLeft (decodeStructureArtifact r "{"))+ assertBool "compiled state is a different envelope" (isLeft (decodeStructureArtifact r (encodeCompiled (compile identity qaBase))))+ assertBool "structure envelope is not compiled state" (isLeft (decodeCompiledOnto qaBase bytes))+ assertBool "encode mismatch" (isLeft (encodeStructureArtifact r ident (compile identity qaPipeline)))+ changed <- right (structureRecipe "direct" 2 "" qaBase)+ changedRegistry <- right (structureRegistry "r" [changed])+ assertBool "registry revision changed" (isLeft (decodeStructureArtifact changedRegistry bytes))+ ]