How decisions are made
Every planned action is resolved to apply, suggest, or suppress. First match wins:
| # | Condition | Outcome |
|---|---|---|
| 1 | Rule disabled, or trigger does not match | skipped silently |
| 2 | if expression throws | skipped, logged, run continues |
| 3 | Budget spent | budget_exhausted |
| 4 | Provider returned nothing | no_answers |
| 5 | Depends on a question that was not asked | unknown_question |
| 6 | Destructive op not in allowDestructive | destructive_not_opted_in |
| 7 | Executor cannot perform the op | unsupported_by_executor |
| 8 | Named template does not exist | missing_template |
| 9 | confidence < gate.suggest | below_suggest_threshold |
| 10 | mode == 'shadow' | shadow_mode |
| 11 | mode == 'suggest', or confidence < gate.auto | suggest |
| 12 | otherwise | apply |
Rule confidence
A rule is as trustworthy as its least certain input, so its gate value is the minimum confidence across the questions it depends on — gateOn when given, otherwise the ids its if expression references. Averaging would let one confident answer carry a coin flip.
A rule with neither is unconditional and has confidence 1.
Answer types
| Type | Value | Confidence |
|---|---|---|
| noul | probability 0–1 | derived as |2p − 1| |
| choice | the selected option | reported by the model |
| score | a fractional level | reported by the model |
A noul carries no confidence of its own, so Dispatch derives one: 0.5 is maximally uncertain, 0 and 1 maximally certain. That lets one gate expression work across all three types.
Scores stay fractional. A score of 1.6 means the distribution sits between two levels. Rounding it before comparing against >= 2.5 would silently move your threshold.
Suppression is the product
suppressed is not diagnostics. In shadow mode it is the entire output — the list you read to decide whether you trust this. Every entry carries the rule that wanted the action, the reason, and the numbers compared:
Suppressed
x label: add needs-repro [needs-repro] shadow_mode
Shadow mode decides and logs but writes nothing.Idempotency
A decision is keyed by sha256(target, item, repo context, question ids, config hash). Two consequences worth knowing:
- Re-delivering the same event serves the stored decision without a provider request.
- Editing your config re-triages, because the config hash is an input. Renaming a rule or moving a threshold invalidates cached decisions, as it should.
The mode is deliberately not part of the key, so promoting shadow → auto does not throw away everything Dispatch has already worked out.