Skip to content

ump.challenge()

challenge() is the debug view of the availability engine. It is meant for development, tests, and tooling, not for rendering directly in a production UI.

ump.challenge(
field: keyof F & string,
values: InputValues,
conditions?: C,
prev?: InputValues,
): ChallengeTrace
type ChallengeTrace = {
field: string
enabled: boolean
directReasons: Array<{
rule: string
reason: string | null
passed: boolean
[key: string]: unknown
}>
transitiveDeps: Array<{
field: string
enabled: boolean
reason: string | null
causedBy: Array<{ rule: string; [key: string]: unknown }>
}>
oneOfResolution: {
group: string
activeBranch: string | null
method: string
branches: Record<string, { fields: string[]; anySatisfied: boolean }>
} | null
}

directReasons includes every rule targeting the field, whether it passed or failed.

Rule-specific metadata is attached where possible:

  • enabledWhen includes the predicate source string, and for check()-based predicates it also includes the preserved source field and current source value.
  • disables includes the source description and source value, plus sourceSatisfied when that helps explain the rule.
  • requires includes dependency satisfaction details, and check()-based dependencies also include the current dependency value.
  • oneOf includes the group, winning branch, and current field branch.
  • anyOf nests inner rule traces.

When a field is blocked by a requires() chain, transitiveDeps walks upstream to the fields that caused the dependency to fail.

In the current implementation this is a flat array of discovered upstream fields, each with a causedBy array describing the failing rules on that dependency.

If the field belongs to a oneOf() group, oneOfResolution shows:

  • the group name
  • the active branch
  • the resolution method
  • the satisfaction status of every branch

That makes ambiguous branch selection much easier to inspect.

const trace = recurrenceUmp.challenge(
'everyHour',
{ everyHour: [9, 17], startTime: '09:00' },
undefined,
{ everyHour: [9, 17] },
)
trace.oneOfResolution
// {
// group: 'subDayStrategy',
// activeBranch: 'interval',
// method: 'auto-detected from prev',
// branches: {
// hourList: { fields: ['everyHour'], anySatisfied: true },
// interval: { fields: ['startTime', 'endTime', 'repeatEvery'], anySatisfied: true },
// },
// }
if (!result.submit.enabled) {
console.table(loginUmp.challenge('submit', values, conditions).directReasons)
}
  • challenge() reuses the same prev semantics as check(), so oneOf() debugging stays faithful to live resolution.
  • It throws if you challenge an unknown field.
  • Because it gathers extra trace detail, it is more expensive than check().