The typed vocabulary of a package-set config. Each pin argument that
has a shape a wrong string could violate gets its own newtype with a
validating IsString instance, so a malformed literal — a revision
that is not a hash, an empty version — is rejected at fold time (the
wisp's own error), not silently written into a pin. With
OverloadedStrings on, a config still writes bare string literals
(hackage "lens" "5.3.4"):
GHC selects the right fromString from each argument's expected type,
so the .vx surface is unchanged and the ergonomics are identical.
Validation lives here, once, rather than being re-checked at every use;
an invalid literal is a partial fromString (an error), which is the
same discipline Horizon.Config.Host already uses for subdirectories.
The accessors expose the underlying value (a Text, or a time for an
IndexState) so the host can wrap it with no re-parsing.
A package version, e.g. 2.2.3.0: non-empty, only digits and dots.
(x components as in an lts pin are a branch, not a package version, so
a package Version is strictly numeric.)
A git commit revision: a hex string (a full 40-char SHA-1 or an
unambiguous prefix of at least 7). This is what a flake pin locks to,
so a non-hex value here is exactly the mistake worth catching early.
A subdirectory within a repo or tarball: non-empty, no whitespace.
(The host still parses it into a real relative directory; this only
rules out the obviously-bad literal at the DSL surface.)
A target platform as cabal names it, ARCH-OS (javascript-ghcjs,
x86_64-linux): an architecture of letters, digits and underscores, a
dash, then the operating system. Whether cabal knows the architecture and
the system is the host's check, which has cabal's tables.
A Hackage index state, as cabal spells one: a UTC time to the second,
2025-03-01T00:00:00Z. Only the index entries published by then are
read, so a later revision cannot change what the set renders. The
literal must be the time's canonical spelling, which is the one form
cabal's index-state and cabal2nix's snapshot both accept.
A repository on a forge, named by host and path with no scheme:
nixlab.openfp.netpackage-setshaskell/horizon-core. The host is part
of the path so a config names the whole location and the generator holds
no forge of its own. Two or more segments, none empty, . or ..; a
host with a dot; nothing a URL would read as a scheme, query or fragment;
and a last segment, the repository, that is a SetName, since a
toolchain base is the flake input named for its repository.
The name a config gives a secure cabal repository, which is also the
name cabal keeps the repository's index under (packages/NAME):
letters, digits, dots, dashes and underscores, e.g. nixlab.
A licence, as one SPDX licence identifier: MIT, BSD-3-Clause,
Apache-2.0, GPL-3.0-or-later. Letters, digits, ., - and +,
beginning with a letter; whether SPDX lists it is the generator's check.
One GHC extension a generated cabal turns on by default
(default-extensions): OverloadedStrings, BlockArguments, or a
No-prefixed one that turns it off. A capitalised identifier; whether
GHC knows it is the generator's check.
The warnings contract of a repository's Haskell builds. werrorwolf
is the estate's: every package also builds as a check with
-Werror on top of its -Weverything. none declares no contract.
The file a test suite runs, relative to its source directory, as
cabal's main-is names it: Main.hs, Laws.hs, Golden/Main.hs.
No empty, . or .. segment and no leading /; ends in .hs.