Errors and recovery
Tiaude is strict by design.
When it rejects an operation, it is usually preventing you from persisting a misleading score.
The recovery strategy is simple:
if incremental state cannot be trusted, rebuild from raw events with scoreUser()Exported errors
Tiaude exports:
ConfigValidationError
InvalidEventError
ConfigMismatchError
OutOfOrderEventError
StateValidationError
StateUserMismatchErrorConfigValidationError
The scorer config is invalid.
Common causes:
- invalid signal type;
- duplicate signal IDs;
- missing threshold on a frequency signal;
- threshold on a non-frequency signal;
- invalid score levels;
- invalid half-life;
- invalid action type.
Recovery:
fix the config before starting the appThis is usually a deployment-time error, not a per-user runtime error.
InvalidEventError
An event or date input is invalid.
Common causes:
- missing event object;
- empty event name;
- invalid timestamp;
- ambiguous date string;
- impossible calendar date;
nowearlier than the event timestamp;nowearlier than the latest event inscoreUser();- time travel on an already computed state.
Recovery:
reject or fix the bad inputFor timestamps, prefer:
new Date().toISOString()ConfigMismatchError
The saved state was produced with an incompatible config.
Common causes:
- signal added or removed;
- signal weight changed;
- threshold changed;
- half-life changed;
- event name changed;
- score level settings changed.
Recovery:
load raw events -> scoreUser() -> persist new stateOutOfOrderEventError
track() received an event older than the saved state.
This can happen with queues, webhooks, retries, or delayed event delivery.
Recovery:
load raw events -> scoreUser() -> persist new statescoreUser() sorts events before scoring, so it is the correct recovery path.
StateValidationError
The supplied previousState is malformed or internally inconsistent.
Common causes:
- missing fields;
- wrong persisted JSON shape;
- corrupted counters;
- impossible timestamps;
- missing signal state;
- invalid residue shape.
Recovery:
discard the unsafe state
load raw events
scoreUser()
persist the rebuilt stateStateUserMismatchError
The supplied state belongs to another user.
This usually indicates a persistence or lookup bug.
Recovery:
do not reuse that state
load the correct state or rebuild for the current userDo not silently recover from this unless you are sure your data access layer is correct.
Recommended helper
import {
ConfigMismatchError,
OutOfOrderEventError,
StateValidationError,
} from "tiaude";
export function shouldRecompute(error: unknown) {
return (
error instanceof ConfigMismatchError ||
error instanceof OutOfOrderEventError ||
error instanceof StateValidationError
);
}Recommended track pattern
try {
const result = scorer.track({
userId,
previousState: user.churnState ?? null,
event,
});
await saveChurnState(userId, result.state);
return result;
} catch (error) {
if (!shouldRecompute(error)) {
throw error;
}
const events = await loadUserEvents(userId);
const result = scorer.scoreUser({
userId,
events,
});
await saveChurnState(userId, result.state);
return result;
}Recommended refresh pattern
try {
const result = scorer.refreshScore({
userId,
previousState: user.churnState,
});
await saveChurnState(userId, result.state);
return result;
} catch (error) {
if (!shouldRecompute(error)) {
throw error;
}
const events = await loadUserEvents(userId);
const result = scorer.scoreUser({
userId,
events,
});
await saveChurnState(userId, result.state);
return result;
}What not to do
Do not repair Tiaude state manually.
Do not create missing signal states yourself.
Do not ignore ConfigMismatchError.
Do not continue after OutOfOrderEventError with the same previous state.
Use scoreUser() from raw events instead.
Final rule
track() and refreshScore() are optimized continuation paths.
scoreUser() is the safe rebuild path.
raw events are the source of truth.