Skip to content

Repository files navigation

requisite

requisite puts trust, confidence, and freshness requirements in Haskell function signatures.

Add requisite ^>= 0.1.0 to build-depends in your Cabal file.

Use focused imports at application boundaries:

import Control.Exception (displayException)
import Requisite.Confidence (Certain, foldGate, gate, mkConfident)
import Requisite.Fresh (check, fetch, seconds, staleReason, staleValue)
import Requisite.Trust (Tainted, Trusted, fromInput, trySanitize)

Requisite is a convenience re-export for examples and small programs. The public library modules are marked Safe; applications need no language extensions and the examples use Haskell 2010.

Trust

repositoryLookup :: Tainted Trusted Int -> IO Customer

raw = fromInput requestField
customerId = trySanitize parseCustomerId raw

Tainted has a hidden constructor. Trusted and Untrusted are uninhabited type-level markers with no data constructors. Both Tainted parameters have nominal roles, so Data.Coerce cannot forge the wrapper, change its trust state, or change its wrapped representation.

mapUntrusted and untrustedValue support inspection and preprocessing without promotion. sanitize and trySanitize are the only trusted transitions; widen only lowers Trusted to Untrusted.

Sanitizer results are evaluated to weak head normal form at the transition. Deep structures remain lazy; force them inside the application policy when validation depends on complete evaluation.

Confidence

scored <- either (fail . displayException) pure (mkConfident prediction 0.98)

foldGate
  commit
  review
  record
  (gate scored)

Probabilities must be finite and in [0, 1]. Default likely and certain thresholds are 0.60 and 0.95. mkThresholds may raise the certain boundary but not lower it. Thresholds uses private positional fields plus explicit accessors, so record update cannot bypass validation.

Gate constructors are private. gateTier inspects the public Tier, while foldGate releases the payload through one of three handlers. Confident intentionally has no payload accessor: confidence-sensitive values are released by classification.

Tier ordering follows confidence: UnsureTier < LikelyTier < CertainTier.

Only the certain handler receives Certain, whose constructor cannot be forged with ordinary constructors or Data.Coerce. Haskell is not linear: a token can be retained and reused after a qualifying gate. It proves that a qualifying classification occurred, not that authorization is single-use. Guarantees also exclude undefined, unsafeCoerce, and other explicit departures from type safety.

Freshness

quote <- fetch (seconds 30) price
result <- check quote
case result of
  Right current -> charge current
  Left expired -> do
    audit (staleValue expired)
    refresh (staleReason expired)

Fresh has no unchecked payload accessor. check and checkAt return either a current payload or a StaleValue; expired payload recovery is therefore explicit and carries its Stale timing information.

The clock API has paired IO and pure forms:

  • fetch and check read the process monotonic clock.
  • fetchAt trusts an injected fetch instant for deterministic code.
  • fromFetchedAt validates a recorded fetch instant against the live clock.
  • fromFetchedAtWith validates named FetchTiming fields without IO.
  • checkAt and remainingAt inject the read instant.

Durations and instant movement saturate on overflow. Values are meaningful only within one process. Whether suspend advances the monotonic clock is platform-dependent, so this API is unsuitable for persisted deadlines or wall-clock expiry.

Payloads are evaluated to weak head normal form before fetch reads the clock. Deeper evaluation remains the caller's responsibility.

A branded withLive API is omitted. Its rank-2, runST-shaped encoding would not make ordinary Haskell payloads affine or strengthen the runtime TTL check.

Errors

Error Show instances are structural. Use displayException for user-facing messages. Payload wrappers such as Confident, Gate, Fresh, and StaleValue deliberately lack Show instances to avoid accidental value logging.

Build and test

The default path is suitable for Hackage builders:

cabal build all -f-compile-fail -f-examples
cabal test requisite-test -f-compile-fail -f-examples
cabal check
cabal haddock all

Compiler-diagnostic contracts and the example are manual flags:

REQUISITE_GHC="$(command -v ghc)" \
  cabal test all -fcompile-fail -fexamples
cabal run -fexamples payment-flow

Compile-fail tests select REQUISITE_GHC when set, otherwise the ghc on PATH; CI sets it explicitly. Diagnostics are whitespace, quote, case, and path-separator normalized. The source filename is verified, then removed before message-specific phrases are checked. The harness also verifies that its case directory exactly matches the manifest and mutation-tests its two former filename false positives. Each case uses an exception-safe system temporary directory.

cabal.project pins the Hackage index state. CI covers the tested GHC range on Linux, macOS, and Windows, and enables both manual flags.

Repository and publishing

The package name is requisite, and its source is slepp/requisite-haskell. Validate a release candidate before uploading because Hackage package versions cannot be replaced. See RELEASING.md for the candidate-first workflow.

License

Copyright 2026 Stephen Olesen.

Licensed under either Apache-2.0 or MIT, at your option.

About

Typed validated values, explicit trust transitions, exhaustive confidence levels, and per-value TTL freshness for Haskell.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages