Appearance
Adding a provider check
The path each existing check follows. Roughly in order:
1. Client
app/lib/providers/<provider>/client.rb — builds requests, applies credentials from configuration, parses responses.
ruby
class Error < Providers::Error; end
class ConfigurationError < Error; end
class RequestFailed < Error; endInherit from Providers::Error
ProviderFetchRun.capture! rescues that class. An error outside the hierarchy escapes as a 500 and no failed run is recorded — the failure leaves no trace at all. An invariant test walks the provider directories and asserts the ancestry, so getting this wrong fails the suite rather than production.
2. Adapter
app/lib/providers/<provider>/<check>_adapter.rb, subclassing BaseAdapter:
#fetch— call the client, or return an injected payload in tests#normalize— provider shape to Verity's.public_result— the sanitised result shown to operators and returned by the API
Rules for normalize and public_result:
- Missing is
nil, not a default - An empty collection means the provider said empty, not that we did not ask
- A positive claim (
verified,valid,clear) needs a positive signal in the payload, never merely the absence of an error
3. Register it
EntityCheck::OPERATOR_CHECK_TYPES and PUBLIC_RESULT_ADAPTERS. Then, as applicable:
| Constant | Add when |
|---|---|
MANUAL_ENTITY_CHECK_TYPES | An operator can run it from the entity page |
ASYNC_ENTITY_CHECK_TYPES | It completes asynchronously |
ENTITY_CREATE_DEFAULT_CHECK_TYPES | It may be configured as an intake default |
4. Resolve configuration
A branch in ProviderFetchRuns::ConfigResolver: provider key, fetch type, lookup identifier, adapter, projection strategy.
Enforce preconditions here, with a message an operator can act on, rather than sending a request you know will fail.
5. Project, or do not
If the check should write into Verity's records, add a projector. If its value is the evidence itself, use NoOpProjector — most of the newer checks do.
6. Surface it
- A label in
config/locales/en.ymlunderentity_checks.manual_labels - If it produces structured evidence, make the fetch-run page render it: add its keys to
STRUCTURED_NORMALIZED_SUMMARY_KEYS, or a reader if the summary is flat
This step is easy to miss
IBAN verification stores its fields flat rather than nested, matched no structured key, and the evidence page reported no normalised evidence while printing those same fields as raw JSON below it.
7. Test it
- Adapter tests with recorded payloads: a documented success, an error, and a sparse or null response
- A controller test for the operator path
- Confirm the invariant suite still passes — it will hold your
public_resultto the empty-payload rule automatically
8. Document it
Add a row to the check catalogue, and to docs/provider-operability-runbook.md with the required configuration and common failure modes.