# Declining a judgement call

Most rules report plain mistakes, and only a fix answers them. A few rules
are judgement calls, where the code can be right as written: two helpers
that match today but differ on purpose, or two constants that share a value
by coincidence. These rules are *declinable*. You either fix the finding,
or decline it on the declaration itself, with a reason.

Here a library function already caps an amount, and `cap_total` does the
same. The `decline` line says why the second one is deliberate, so
`library.similar_exists` doesn't report it:

```jadpo
type Stock = Int {
    min: 0
}

library rounding {
    /// The amount, capped at a thousand.
    function capped(amount: Stock) -> Stock {
        if (amount > Stock(1000)) {
            return Stock(1000)
        }
        return amount
    }
}

/// The basket total, capped for the till.
decline library.similar_exists: "Caps the basket for the till, which changes on its own; rounding.capped caps one line"
function cap_total(total: Stock) -> Stock {
    if (total > Stock(1000)) {
        return Stock(1000)
    }
    return total
}
```

## Writing a decline

A decline is a line of the form `decline <rule>: "<reason>"`, directly
above the declaration or member it covers, after its `///` comments. So
the order is `///` comments, then `decline` lines, then the declaration.

The rule is written `area.name` with no spaces, and either part may be a
reserved word. A decline with no declaration under it is
`syntax.expected_declaration`, and one inside a body is
`syntax.expected_statement`.

A decline covers its one rule on its one declaration: the innermost
declaration or member around the finding's location. So a decline on a
type covers a finding on the type, not one on its field.

The reason is required. A missing or blank one is `decline.no_reason`, and
the finding stays declined meanwhile, so you see one error, not two.

An agent or a person may decline. Every declinable error's `next:` line
ends with ", or write `decline <rule>: "<why>"` above it if this is
deliberate".

## Which rules can be declined

| Rule | Page | Reports |
|---|---|---|
| [`library.similar_exists`](../errors/library.similar_exists.md) | Project layout and modules | A library function already does the same |
| [`constant.similar_exists`](../errors/constant.similar_exists.md) | [Logic](logic.md) | Another constant of the same type holds the same value |
| `translation.field_unused` | [Configuration and time](configuration-and-time.md) | No wording in a language uses one of a message's fields |
| `package.standard_candidate` | [Integrations](external-services.md) | A provider field looks like a standard type the package doesn't use |
| [`type.batch_order_updatable`](../errors/type.batch_order_updatable.md) | [Jobs and events](jobs-and-events.md) | A batch walks a query ordered by a field an update can change |
| [`step.late_failure`](../errors/step.late_failure.md) | [Jobs and events](jobs-and-events.md) | An action can fail for good after a changing call took effect |
| `email.field_unused` | [Built-in services](built-in-services.md) | An email's field is written by no template and no subject |
| `email.health_problem` | [Built-in services](built-in-services.md) | An email's template fails a check of the mail health score |
| `rate_limits.counted_in_memory` | [Built-in services](built-in-services.md) | A deployable built for Workers would count its rate limits in each instance's memory |
| `type.standard_exists` | [Standard library](standard-library.md) | A type has a standard type's shape under another name |
| `type.variant_retired` | [Standard library](standard-library.md) | Code writes a retired code |
| [`query.nearest_exact`](../errors/query.nearest_exact.md) | [AI](ai.md) | Nearest-neighbour order on a vector too long for an index reads every record |

Each of these can be right as written. Two helpers can differ on purpose,
two constants can share a value by coincidence (a VAT rate and a fee), a
shape can match a standard type by chance, and code that reads old records
names a withdrawn code on purpose.

Plain mistakes are never declinable. That includes retries of a call to an
outside service that outlast a route's deadline
([Integrations](external-services.md)), which can't work as written. Naming
any other rule in a decline, or a rule Jadpo doesn't have, is
[`decline.not_declinable`](../errors/decline.not_declinable.md), which names
the nearest declinable rule.

## Declines that no longer apply

A decline whose rule no longer fires on its declaration is
[`decline.unused`](../errors/decline.unused.md), so declines don't outlive
their reason. A second decline of the same rule on one declaration is
unused too.

`decline.unused` is judged only once every other finding is answered: no
syntax errors, and every other error is itself declinable or about a
decline. That's because another error can stop a rule from firing.

## A declined finding is still an error

In JSON, a declined diagnostic keeps `"severity": "error"` and gains
`"declinable"`. `jadpo help --json` gives each rule's `declinable`, and
`jadpo check --json` and `jadpo test --json` have no `warnings` counts.

## Listing and clearing declines

`jadpo declines list` shows every decline with its rule, place and reason.
`jadpo declines clear` removes them, so each finding comes back to be fixed
or declined again. Both take `--rule <id>`, a path and `--json`
([Commands for records, findings and review](commands-for-records-findings-and-review.md)).

```text
$ jadpo declines list
Declines
  constant.similar_exists  domains/sales/libraries/fees.jadpo:4
    A courier's charge, not the fee
  library.similar_exists   domains/sales/libraries/rounding.jadpo:12
    Caps the basket for the till, which changes on its own

✓ 2 declines  2 rules · 2 files

$ jadpo declines clear --rule library.similar_exists
✓ Removed 1 decline from 1 file

→ jadpo check  shows each finding again, to fix or decline
```

When a reason doesn't fit beside its row, `list` puts every reason on the
lines under its row, wrapped.

Use `clear` after a new standard type lands, after a compiler upgrade makes
a rule smarter, and for a periodic sweep. It stops on syntax errors, as
every command that writes source does; `list` lists whatever the state of
the code.

In the editor, "Decline with a reason" on a declinable finding writes the
line with an empty reason for you to fill in, and "Remove decline" on a
decline line removes it. Both run the same code as the commands.

`jadpo approval` lists every decline a change adds or edits, so declining
by reflex shows in review ([Records and manifest](records-and-manifest.md)).

## Rules triggered by time passing

A rule triggered by time passing, such as a
[feature flag](feature-flags.md) whose date has
expired, is judged at today's date by `check`, so CI fails a new change. It
is judged at the commit's date by `jadpo build`, so rebuilding a merged
revision for a hotfix is never blocked by a date passing. It isn't
declinable: extending the date is the reviewed choice.

Not built yet: `jadpo approval`, and the rule for an expired feature flag.
