Problem
The interface between createTestnetEnv (step 1) and cardanoTestnet (step 2) is entirely implicit — it is a directory layout with naming conventions scattered across Defaults.hs, Configuration.hs, and Cardano.hs. Critical information like node count and types is not persisted, forcing the user to re-specify CLI options consistently across both invocations. Other data like testnetMagic must be re-parsed from genesis files.
This makes the cross-phase contract fragile: adding a new file in step 1 and forgetting to read it in step 2 is not caught by the compiler.
Proposed fix
Define a TestnetEnvManifest type that captures everything step 1 produces that step 2 needs:
data TestnetEnvManifest = TestnetEnvManifest
{ manifestNodes :: NonEmpty NodeManifestEntry
, manifestUtxoKeys :: Int
, manifestTestnetMagic :: Int
, manifestCreatedAt :: UTCTime
, manifestEra :: AnyShelleyBasedEra
} deriving (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)
data NodeManifestEntry = NodeManifestEntry
{ manifestNodeId :: Int
, manifestNodeType :: NodeType -- SPO | Relay
} deriving (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)
createTestnetEnv writes testnet-env.json as its final step.
cardanoTestnet reads it as its first step when running from a pre-existing env.
- In the direct
cardano path, the manifest is constructed in-memory and passed directly (no disk round-trip needed).
The manifest file is human-readable, so users can inspect what create-env produced.
Adding a new cross-phase datum = adding a field to TestnetEnvManifest -> compiler forces both sides to update.
Depends on
Type: Refactoring
Effort: Medium (~60 lines: type definition, ToJSON/FromJSON, write in createTestnetEnv, read in cardanoTestnet)
Risk: Low-medium (new serialisation code; failure mode is a clear parse error, not silent breakage)