Repository navigation
Releases
Releases · doeixd/discern
0.5.0
Mistakes that used to compile or replay quietly are now refused.
Upgrading from 0.4.0
Most programs upgrade without changes. Check for these:
- A
classifyorratewhosecriteriais typedstring(for example a
Record<string, string>loaded at runtime), contains a template label, or is
empty no longer compiles. Wrap an EffectDecision.classifywith
Discern.decisionand read it withwhere, or route with
Procedure.registry. aboveoratLeastwith amissBelowabove the threshold, andbelowor
atMostwith amissAboveunder it, now throwInvalidThresholdErrorwhen
the pattern is built.betweenwith its ends reversed, such asrisk.between(0.6, 0.4)or
severity.between("major", "minor"), now throwsInvalidRangeErrorwhen the
pattern is built. It could never match.Eval.calibrateneeds at least one value invalues. A variable typed
V[]must be shown non-empty, for example as[first, ...rest].- Tests that match the old threshold or id-collision messages with a regex
should checkinstanceof InvalidThresholdErroror
instanceof DecisionIdCollisionErrorinstead. - A stored observation that fails validation now fails replay and cache reads,
where it used to be used as-is. Recordings Discern wrote itself are
unaffected.
Added
Discern.Model.ObservationsandDiscern.Model.Observationare
schemas, and the types of the same names now derive from them. A snapshot
read back from outside the program can be decoded with
Schema.decodeUnknownSync(Discern.Model.Observations)rather than cast, so a
malformed or foreign recording is refused at the boundary.Discern.Model.isInvalidObservationrecognises the failure raised when a
stored answer fails validation (see Fixed).Discern.InvalidThresholdError,Discern.InvalidRangeErrorand
Discern.DecisionIdCollisionErrorname the mistakes Discern catches while a
policy is being built. All three are thrown, not failures on the error
channel: they are faults in the policy's definition, which no run can
encounter, so they stay out of every run's error type.
Changed
classifyandraterefuse label sets the compiler cannot enumerate.
Acriteriawhose labels arestring(such as aRecord<string, string>),
include an infinite template such as`tone-${string}`, or are empty is
now a compile error, standalone and throughDiscern.on(schema). Every
label check compares against the label union, so with an open set a typo in
is, a duplicatecaseOf, or a missing case underexhaustiveall
compiled; with an empty set,exhaustivepassed without a single case.
Labels known only at runtime go throughDiscern.decision, read with
where, or throughProcedure.registry, whose routing is unaffected.above,atLeast,belowandatMostcheck their miss bound. A
missBelowabove the threshold, or amissAboveunder it, used to leave a
pattern silently two-valued. It now throwsInvalidThresholdError, asband
andis(label, { match, miss })already did.betweenchecks its ends, on probabilities and on ratings. A low end
above the high end made a pattern that could never match, so a case built on
it silently never fired. It now throwsInvalidRangeError. Equal ends are
allowed and match exactly that value or level.Eval.calibraterequires at least one candidate invalues, typed as a
non-empty tuple. An untyped caller passing[]gets a defect before any
model call, rather than one after the sweep.Discern.askwith an unscoped decision returns a defect instead of
throwing. The type already refuses it; an untyped caller now sees the
failure when the Effect runs, as with any function that returns an Effect.
Fixed
replayingandcachingvalidate the answers they read back from a
store. Interceptors sit aboveDecisionModelvalidation, so a stored answer
was handed to the policy as-is: a hand-edited recording with a probability of
7, or a label the decision does not have, replayed as a confident answer.
Stored answers now pass the same checks as a provider's, and a bad one fails
with anAiErrorthatisInvalidObservationrecognises. Honest recordings
replay exactly as before. Each check records aDecisionModel.decidespan,
so cache and replay hits now appear in traces.
Documentation
- Ratings:
atLeast,atMostandbetweencompare a probability-weighted
position that can fall between two levels, soatLeast("major")and
atMost("minor")can both miss the same answer. The README and the method
docs explain the gap and the ways to place it. - New README sections on finite label sets, threshold and range validation,
decoding a snapshot, and what replay checks.