---
title: "check’s gates"
url: https://services.dev/docs/reference/check
---
# check’s gates

URL: https://services.dev/docs/reference/check

> Every gate npx services.dev check runs before you sell, the code its --json output prints, and what fixes it. A failing gate exits 1.

`check` runs 40 gates on every Service before you sell; a failing gate exits 1 and prints its code, why and the fix in `--json`. 30 have their own page: the file, what `check` prints and the fix as git prints it.

## What an agent reads

| Gate | Means | Fix |
|---|---|---|
| [`description_too_long`](https://services.dev/docs/reference/check/description_too_long.md) | The tool description is over 30 words, so agents skim past it. | Shorten description. |
| [`price_unstated`](https://services.dev/docs/reference/check/price_unstated.md) | The tool description omits the price or when it's charged. | Keep the price sentence in it. |
| [`agent_cannot_buy`](https://services.dev/docs/reference/check/agent_cannot_buy.md) | An agent couldn't order, wait for and accept a job. | Run npx services.dev dev and read its job. |
| [`toolset_too_large`](https://services.dev/docs/reference/check/toolset_too_large.md) | The buyer's MCP toolset is over its token budget, so an agent's context fills with tools. | Move Services to a brand of their own, or rows to the sandbox toolset: the order tools already fold into one. |
| [`instructions_too_long`](https://services.dev/docs/reference/check/instructions_too_long.md) | The MCP server's instructions are over 120 words. | Move Services to a brand of their own: the instructions name each Service that charges at its own time or waits on a party. |

## Steps

| Gate | Means | Fix |
|---|---|---|
| [`step_unreachable`](https://services.dev/docs/reference/check/step_unreachable.md) | A step no edge reaches never runs. | Fix its after, or route there with next. |
| [`review_after_attest`](https://services.dev/docs/reference/check/review_after_attest.md) | A person attests work no review has passed: the review runs after the signature. | Move the review ahead of the step that signs, so the person signs work the review has already passed. |
| [`release_unproven`](https://services.dev/docs/reference/check/release_unproven.md) | price.release names no step that runs. | Name a step that runs. |

## Dates

| Gate | Means | Fix |
|---|---|---|
| [`clock_unowned`](https://services.dev/docs/reference/check/clock_unowned.md) | A clock's owner can never resolve: a role the Service doesn't have, or of_record with no member of record on its subject. It would run vacant, with no one to answer for the date. | Name a role the Service has, 'brand' or 'buyer', or give the subject an of_record. |
| [`clock_due_unreadable`](https://services.dev/docs/reference/check/clock_due_unreadable.md) | A clock's due function can't give a date for the Service's own example order: it throws, or returns something that isn't a date. On a real order the clock would never open, and the owner console would alarm. | Return a civil date such as '2027-04-15' from what the order has, or nothing until it has it. |

## Money

| Gate | Means | Fix |
|---|---|---|
| [`price_below_cost`](https://services.dev/docs/reference/check/price_below_cost.md) | A delivered job could lose money: payout, nested Services and the AI cap add up to more than the price. | Raise price.usd, or lower payout. |
| [`upgrade_below_payout`](https://services.dev/docs/reference/check/upgrade_below_payout.md) | The approved price adds less than the person's payout. | Raise price.approved past the payout. |
| [`rail_conflict`](https://services.dev/docs/reference/check/rail_conflict.md) | A role's pay.rail contradicts the rail of the row for its act. | Leave pay.rail out, so the row's rail holds, or make the two agree. |
| [`rush_uncapped`](https://services.dev/docs/reference/check/rush_uncapped.md) | A role is paid from the buyer, its Pool's fill ladder raises the fee, and nothing caps what the buyer pays for it. | Declare policy.max_rush: the most an order lets the ladder add, in dollars. Or pay the role from the price. |
| [`fill_underwater`](https://services.dev/docs/reference/check/fill_underwater.md) | The Pool's fill_cap lets the ladder raise a brand-funded fee past what the job leaves: a job filled at the cap loses money. | Lower the Pool's fill_cap, or leave it out so the default holds: the lower of 50% and what the margin leaves. |

## Rules and people

| Gate | Means | Fix |
|---|---|---|
| [`reserved_act_unstaffed`](https://services.dev/docs/reference/check/reserved_act_unstaffed.md) | A step performs a reserved act, and no role that does it holds the credential its row reserves it to. | Give the step to a role that holds the credential, or set the role's credential to one that meets it. |
| [`act_unruled`](https://services.dev/docs/reference/check/act_unruled.md) | A step names an act the rules table has no row for, under its Service or shared. There is no fallback. | Add a row for the act to the rules table, with its position, or name an act the table has. |
| [`rule_unread`](https://services.dev/docs/reference/check/rule_unread.md) | The rules table binds a name nothing reads: a key that names no Service file, or a row no step performs that is neither an advice class nor refused. | Rename it to what it means, or take it out. |
| [`where_unresolved`](https://services.dev/docs/reference/check/where_unresolved.md) | A reserved row reads where from a party's region, and the Service takes no input that names it, so an order could never say where the act happens. | Add the input the row reads, such as client_region: 'jurisdiction: such as US-CA', or set the role's where. |
| [`region_unqualified`](https://services.dev/docs/reference/check/region_unqualified.md) | A rules row's regions entry isn't an ISO 3166 code: a bare state such as NY is no country, and CA is Canada. | Write the subdivision as US-NY, or a country such as US for every state. |
| [`self_unruled`](https://services.dev/docs/reference/check/self_unruled.md) | price.self prices the client performing reserved acts themselves, and no reserved row the Service performs allows self. | Mark the rows the client may perform with self: true, or take price.self out. |
| [`advice_without_adopt`](https://services.dev/docs/reference/check/advice_without_adopt.md) | The rules reserve an advice class for the Service, and its talk names no role from a Pool that holds the row's credential to adopt held answers. | Set talk: \{ adopt: `'<role>'` \} to a role from a Pool whose credential meets the advice row, or turn talk off with talk: false. |
| [`client_decision_gated`](https://services.dev/docs/reference/check/client_decision_gated.md) | A rules row reserves a decision to the client, and the step isn't an ask to the client, or a guard ahead of it refuses what the client would decide. | Make the step an ask to the client party, and turn the guard into a signal: the client sees every offer. |
| [`sheet_without_consent`](https://services.dev/docs/reference/check/sheet_without_consent.md) | A Pool's sheet names the job's parties to the members who read it, and the client hasn't consented before the first Task posts. | Add a consent ask to the client ahead of the first role step: \{ ask: 'client', to: 'consent' \}. |
| [`custodian_unqualified`](https://services.dev/docs/reference/check/custodian_unqualified.md) | A subject's custodian ladder leaves the brand of record for a role whose credential the brand doesn't hold. | Put a role that holds the credential ahead of 'brand' in custodian, or declare the brand's credential in brand.ts. |
| [`pool_role_unknown`](https://services.dev/docs/reference/check/pool_role_unknown.md) | A Service's role names a Pool that has no role by that name, so no member could ever be offered its Task. | Name the role as the Pool does, or add it to the Pool's roles. |
| [`fill_bypasses_reserved`](https://services.dev/docs/reference/check/fill_bypasses_reserved.md) | A fill rung could offer a reserved act to someone who doesn't hold its credential. | Take the rung out, or add a staff role that holds the credential. |
| [`brand_credential_missing`](https://services.dev/docs/reference/check/brand_credential_missing.md) | A rules row names a credential the brand holds, and the Brand doesn't declare it. | Declare it in brand.ts, under Brand(\{ credentials \}), with the verifier that keeps it fresh. |

## Names and surfaces

| Gate | Means | Fix |
|---|---|---|
| [`surface_drift`](https://services.dev/docs/reference/check/surface_drift.md) | A surface serves a call with no row, or lacks a live row. | Add the row, or render it. |
| [`service_id_reserved`](https://services.dev/docs/reference/check/service_id_reserved.md) | A Service's file name, or a portal's, takes a word services.dev's own routes use. | Rename the file, or the role. |
| [`word_collision`](https://services.dev/docs/reference/check/word_collision.md) | A Brand.words value takes a generic word, a word an operation's id is spelled with or a path services.dev already serves, or makes two rows render the same, so two things would read the same. | Choose a word of your own for it. |

## What check --json prints

```json title="npx services.dev check --json"
{
  "ai": {
    "cents_per_call": 42,
    "simulated": true
  },
  "mcp": {
    "tools": 13,
    "tokens": 4525,
    "sandbox": 9,
    "budget": 8000
  },
  "services": [
    {
      "path": "services/store-listing.ts",
      "lines": 5,
      "steps": "draft · ai → review · ai · adversarial · then a person approves",
      "ok": false,
      "price": "$29 per job, or $30 with a person's approval, within 48 hours, charged on delivery.",
      "who": "AI delivers unless the buyer pays for a person's approval.",
      "agent": {
        "tool": "order_store_listing",
        "words": 29,
        "job": "job_7Hq2",
        "status": "accepted",
        "on": null,
        "charged": 2900,
        "person": false,
        "why": ""
      },
      "ledger": "+$29.00 · AI $0.84 (example) · you $28.16",
      "approved_ledger": "+$30.00 · AI $0.84 (example) · person $40.00 · you −$10.84 · approved",
      "outcome": null,
      "margin": {
        "floor": {
          "cents": 2320,
          "pct": 80
        },
        "approved": {
          "cents": -1600,
          "pct": -53
        },
        "own": -1600,
        "stop": 4600,
        "nested": []
      },
      "apps": [
        "customer portal",
        "delivery portal",
        "owner console"
      ],
      "failures": [
        {
          "code": "price_below_cost",
          "reason": "A delivered job could lose money: payout, nested Services and the AI cap add up to more than the price.",
          "next": "Raise price.usd, or lower payout.",
          "doc_url": "https://services.dev/docs/reference/check/price_below_cost"
        },
        {
          "code": "upgrade_below_payout",
          "reason": "The approved price adds less than the person's payout.",
          "next": "Raise price.approved past the payout.",
          "doc_url": "https://services.dev/docs/reference/check/upgrade_below_payout"
        }
      ]
    }
  ]
}
```

## Each failure

| Field | Type | Always | Means |
|---|---|---|---|
| `code` | `string` | yes | The gate's code, the word an agent branches on. |
| `reason` | `string` | yes | What the gate means, the same words on every failure of it. |
| `next` | `string` | yes | What to change: the gate's fix, then what this run found, naming the step, row or file. |
| `doc_url` | `string` | yes | The gate's page: the failing file, what check printed and the fix. |
| `file` | `string` | no | The file at fault, from the brand's folder: rules.ts, brand.ts, a Pool's file or the Service's own. |
| `line` | `number` | no | The line in that file, when check can find it. |
| `path` | `string` | no | Where in the definition, such as rules.services['translation'].sign or deliver.choose. |
| `suggest` | `{ path: string; value: string }` | no | A value that fixes it, to write at its path. |

## Gates on the money and the live brand

Nine more read the ledger, the brand's fee policy, a door's terms and the deployed brand:

| Gate | Fails when |
|---|---|
| `ai_unfunded` | a Service paid only on an outcome runs AI with no budget that funds it |
| `fee_policy` | a price uses a fee form the brand's fee policy forbids |
| `member_charged` | a ledger line charges a member to work |
| `charter_weakened` | a Pool's keys, an import or a door's terms would remove or narrow a line of the [member charter](https://services.dev/docs/people/pool/door.md#the-member-charter) |
| `pay_below_floor` | an importing role pays below the minimum the exporter publishes for it: [importing](https://services.dev/docs/people/pool/share.md#import-a-pool-another-operator-runs) |
| `party_missing` | an ask names a party no input supplies |
| `breaking_change` | `check --against live` finds a removed field or a newly required input |
| `not_regenerable` | a generated surface was edited by hand, so the definition can't regenerate it |
| `learning_without_use` | a learning key is set where no step reads what it learns |

---

- Up: [Your first paid job](https://services.dev/docs.md)
- Previous: [Definition errors](https://services.dev/docs/reference/definition-errors.md)
- Next: [description_too_long](https://services.dev/docs/reference/check/description_too_long.md)
