---
title: "The doctor contract"
description: "Metadata, shared analysis, and inspectable findings for a doctor and its fixtures."
---

> Documentation Index
> Fetch the complete documentation index at: https://anydoctor.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# The doctor contract

A doctor is a single JS file. It exports a `meta` object and a
`doctor(ctx)` function. Companion fixtures describe the cases used to verify it.

Here's what the metadata for a doctor looks like:

```js
export const meta = {
  id: "refresh-session",
  description: "Direct discarded session refresh promises.",
  severity: "warning",
  blindSpots: [
"Only direct named imports from @acme/session are recognized.",
"Stored promises and wrapper calls are not followed.",
"Without call analysis this check does not run.",
  ],
  checks: [{
id: "discarded-refresh",
description: "The session refresh call's result is discarded.",
severity: "warning",
revision: 1,
needs: ["calls"],
onUnknown: "skip",
reportingUnit: "occurrence",
claim: "A direct named refreshSession import from @acme/session is called as a discarded expression.",
lookalikes: ["Awaited, returned, passed, or stored results", "Shadowed local functions"],
impact: "Discarding this promise can leave refresh failures unhandled.",
why: "The identified package call is a standalone discarded expression.",
fix: "Await or return this promise where appropriate for the caller's contract.",
  }],
};
```

This metadata accompanies the complete implementation and fixtures in
[Writing your first doctor](/authoring/first-doctor). That example assumes the
named package export returns a promise; metadata alone does not establish it.

## The confinement

The runtime gives the doctor a read-only context and nothing more.

Doctors run under Node's permission model: file writes, subprocesses,
and native addons are denied by Node itself, and an import guard refuses
every module a doctor tries to reach. Network globals are deleted before
doctor code runs, and a checker scans the doctor file for network calls.
Nothing phones home.

## Check fields

| Field | What it is |
| ----- | ---------- |
| `id` | Stable check name; appears as `doctor/check-id` |
| `description` | The heading shown for each finding |
| `severity` | `error`, `warning`, or `info`; severity does not establish certainty |
| `revision` | Bump when the check's *meaning* changes; cosmetic edits keep it |
| `claim` | The observable condition the check states. Required to certify |
| `lookalikes` | Shapes that look like the claim but are fine |
| `needs` | Required analysis capabilities, such as `["calls"]`; declare these when using optional analysis |
| `onUnknown` | `"narrow"` or `"skip"` when required analysis is unavailable; implement and test this behavior |
| `reportingUnit` | `occurrence`, `file`, or `project` — what one finding covers |
| `impact` / `why` / `fix` | The explanation carried into every report and JSON scan |

The `claim` is the load-bearing field. A check without a claim cannot be
certified, because eloquent impact prose is not a testable statement.
A nonempty claim is not proof that the implementation establishes it. Verification
also requires lookalikes, and an `onUnknown` policy when `needs` is declared.
Declare a reporting unit on new checks and inspect skipped coverage. More on this
in [honest checks](/authoring/honest-checks).

## Report an inspectable finding

`evidence` belongs to an emitted finding, not the check metadata:

```js
ctx.report.finding({
  rule: "discarded-refresh",
  file,
  line: call.line,
  column: call.column,
  evidence: { endLine: call.endLine, endColumn: call.endColumn },
});
```

Lines are one-based and columns are zero-based. Supply the expression's end
position for multiline evidence. Distinct occurrences on one line need distinct
columns; location and evidence help reviewers inspect the claimed operation.

Doctors receive shared structural and binding analysis through `ctx`. Use these
facts to establish identity and scope; keep framework semantics in the doctor.
Do not import a parser or create a private text-based substitute for semantic
facts. See [testing and evaluating doctors](/authoring/testing-doctors) for
location witnesses, unavailable-analysis fixtures, and independent evaluation.

## Where doctors live

Three locations, in priority order:

1. `./doctors/` in the repo being scanned
2. `~/.any-doctor/doctors/` (global, `--global` when generating)
3. the bundled pack (lowest priority)

Fixture files (`*.fixtures.mjs`) beside a doctor are its test data, not
target source — scans skip them.

Source: https://anydoctor.dev/authoring/contract/index.mdx
