check’s gates
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.
| Gate | Means | Fix |
|---|---|---|
description_too_long | The tool description is over 30 words, so agents skim past it. | Shorten description. |
price_unstated | The tool description omits the price or when it's charged. | Keep the price sentence in it. |
agent_cannot_buy | An agent couldn't order, wait for and accept a job. | Run npx services.dev dev and read its job. |
toolset_too_large | 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 | 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. |
| Gate | Means | Fix |
|---|---|---|
step_unreachable | A step no edge reaches never runs. | Fix its after, or route there with next. |
review_after_attest | 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 | price.release names no step that runs. | Name a step that runs. |
| Gate | Means | Fix |
|---|---|---|
clock_unowned | 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 | 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. |
| Gate | Means | Fix |
|---|---|---|
price_below_cost | 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 | The approved price adds less than the person's payout. | Raise price.approved past the payout. |
rail_conflict | 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 | 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 | 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. |
| Gate | Means | Fix |
|---|---|---|
reserved_act_unstaffed | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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. |
| Gate | Means | Fix |
|---|---|---|
surface_drift | A surface serves a call with no row, or lacks a live row. | Add the row, or render it. |
service_id_reserved | 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 | 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. |
{
"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": {| 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. |
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 |
pay_below_floor | an importing role pays below the minimum the exporter publishes for it: importing |
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 |