Requisite puts data-handling requirements in Swift signatures. It provides three focused tools:
| Type | Purpose |
|---|---|
Tainted<Value, State> |
routes external input through an application policy |
Confident<Value> |
validates a probability and produces an exhaustive action gate |
Fresh<Value> |
checks an individual TTL and preserves stale values for recovery |
The package has no dependencies and supports Swift 5.9 or later. Swift 5.9 is
the first stable release with the ownership model evaluated for Certain, and
it remains available as an official Linux toolchain; the public API does not
require Swift 6 language mode.
dependencies: [
.package(url: "https://github.com/slepp/requisite-swift", from: "0.1.0")
]Add Requisite to the target that uses it.
Tag data where it enters the application. A sink states the trust state it accepts:
import Requisite
enum InputError: Error { case invalidID }
func loadCustomer(_ id: Tainted<Int, Trusted>) { /* ... */ }
let raw = Tainted<String, Untrusted>.fromInput(requestField)
let customerID = try raw.sanitized { value in
guard let id = Int(value) else { throw InputError.invalidID }
return id
}
loadCustomer(customerID)The policy closure defines Trusted for that destination. asUntrusted()
lowers a trusted value when an API accepts a weaker state. There is no general
map operation that could alter a trusted value while retaining its tag.
Confidence accepts only finite values in 0...1. Confident keeps its value
private until gate(using:) classifies it:
let forecast = try Confident(event, confidence: 0.97)
switch forecast.gate() {
case .highConfidence(let proof, let event):
execute(proof, event)
case .likely(let event):
requestReview(event)
case .unsure(let event):
record(event)
}Gate is @frozen: its three cases are an API commitment, and external
clients can switch exhaustively without @unknown default. Adding another case
would be a breaking change. Certain has no public initializer; safe client
code receives it only from the highest tier. Custom Thresholds may raise its
boundary but cannot lower it below 0.95.
Certain remains copyable. Swift 5.9 can express it as ~Copyable, but the
associated Gate would then also be noncopyable. Clients would need
ownership-aware pattern matching, and the oldest supported compiler could not
use the gate with ordinary generic and protocol-based utilities. That design
would enforce one-shot use, not stronger construction control. This capability
records a completed check rather than ownership of a consumable resource, so
the interoperability cost is not justified.
Once issued, a Certain value may be copied and reused for more than one call.
It does not provide one-shot authorization. Applications that need that
property should add a domain-specific consumable resource rather than infer it
from confidence.
Fresh records a monotonic fetch instant and a per-value TTL:
let quote = try Fresh(price, timeToLive: .seconds(30))
switch quote.result() {
case .success(let current):
charge(current)
case .failure(let expired):
archive(expired.value)
refresh(after: expired.stale)
}value() throws Stale while retaining the wrapper. result() returns
StaleValue<Value> in its typed failure branch, preserving both the expired
value and its timing information. The explicit-time initializer supports
controlled fetch instants and validates them against the actual monotonic clock;
callers cannot substitute a later now to admit a future timestamp. Negative
TTLs are rejected.
ContinuousClock.Instant is meaningful only within the current process and
clock epoch; do not persist it.
Requisite does not import Foundation. Errors conform to Error and
CustomStringConvertible, not LocalizedError; presentation layers can map
their typed payloads to localized messages.
The Rust package's Live/with_live API is not included. Swift 5.9 has no
generative lifetime or higher-ranked closure mechanism that prevents a wrapper
from being returned or stored outside one specific closure invocation.
Nonescaping closures alone do not establish that guarantee. Swift 6.2's
~Escapable constrains function-scope escape, but does not create a fresh
per-call brand for this API. A closure helper would therefore express a
convention rather than the Rust contract.
The trust wrapper is native rather than based on swift-tagged. Requisite
needs restricted construction and state-specific transitions; a small local
type makes those rules visible and avoids adding a dependency.
The Apple Swift 6 toolchain used for formatting includes swift format; the
official Swift 5.9 Linux image does not, so formatting is gated separately from
5.9 compatibility. Linux Swift 6 language-mode builds, examples, and compiler
contracts run in CI, while runtime tests use the Apple job because Swift
6.2's generated Linux XCTest discovery shim crashes before executing package
tests. Swift 5 language-mode tests continue to run on Linux 5.9 and 6.2.
swift format format --configuration .swift-format --in-place --recursive Package.swift Sources Tests Examples
swift format lint --configuration .swift-format --recursive --strict Package.swift Sources Tests Examples
swift build -c release -Xswiftc -swift-version -Xswiftc 5 -Xswiftc -warnings-as-errors
swift test -Xswiftc -swift-version -Xswiftc 5 -Xswiftc -warnings-as-errors
swift build -c release -Xswiftc -swift-version -Xswiftc 6 -Xswiftc -warnings-as-errors
swift test -Xswiftc -swift-version -Xswiftc 6 -Xswiftc -warnings-as-errors
swift run RequisiteExample
SWIFT_LANGUAGE_VERSION=5 Scripts/check-api-contracts.sh
SWIFT_LANGUAGE_VERSION=6 Scripts/check-api-contracts.sh
Scripts/check-documentation.shThe contract command type-checks a valid external client, then verifies that
Swift rejects direct Certain and Trusted construction, upward trust
widening, untrusted sinks, clock substitution, and use of a confident value
without a gate. SwiftPM does not provide a built-in compile-fail test target,
so these checks are a separate CI step. Documentation validation uses the
package symbol graph and the DocC compiler from an Apple toolchain. .spi.yml
selects Requisite for Swift Package Index documentation.
Licensed under Apache-2.0 or MIT, at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.