rule_unread

The running jobjob_7Hq2 · delivered · document.fr-CA.pdf · $49.00 charged

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.

FixFails inExit
Rename it to what it means, or take it out.rules.ts:8 · rules.services['translation'].notarize1

Owned by Build it: the page that explains what this gate holds.

npx services.dev check
  ✗ 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. rules.services['translation'].notarize is a row no step of translation performs. A row binds nothing until a step does its act.
    at rules.ts:8 · rules.services['translation'].notarize
✗ 1 to fix
rules.ts
import { Rules } from 'services.dev'

export default Rules({
  version: '2026-10',
  services: {
    translation: {
      sign: { reserved: 'sworn translator', where: '*', position: 'A sworn translator signs a translation filed with a registry.' },
      notarize: { reserved: 'sworn translator', where: '*', position: 'A notary seals a translation for a registry.' },
    },
  },
})
git add -A && git diff --staged
diff --git a/rules.ts b/rules.ts
index ab66914..ea708e3 100644
--- a/rules.ts
+++ b/rules.ts
@@ -5,7 +5,6 @@ export default Rules({
   services: {
     translation: {
       sign: { reserved: 'sworn translator', where: '*', position: 'A sworn translator signs a translation filed with a registry.' },
-      notarize: { reserved: 'sworn translator', where: '*', position: 'A notary seals a translation for a registry.' },
     },
   },
 })
npx services.dev check
✓ ready to sell · 1 Service · it keeps at least 80% of every job
services/translation.ts
import { Service } from 'services.dev'
import rules from '../rules.ts'

export default Service({
  name: 'Translation',
  input: { document: 'file', to: 'language: a BCP 47 tag, such as fr-CA' },
  output: { document: 'file' },
  rules,
  roles: { translator: { credential: 'sworn translator' } },
  deliver: {
    translate: 'ai',
    harden: { review: 'adversarial', lenses: ['meaning', 'terms'] },
    sign: { by: 'translator', stamp: true },
  },
  price: 89,
})

How the legal example passes this gate, in lines read from its files.

examples/legal/rules.ts
  'trademark-response': {
// …
    fee: { open: true, position: 'Paying the office its fee is a delivery act.' },
// …
shared: { litigate: { refused: true, route: 'https://referrals.example.test/litigation', position: 'Litigation is retained directly, never ordered here.' } },
examples/legal/services/trademark-response.ts
deliver: {
// …
  fee: { routed: true, submit: 'https://filings.example.test/fees', to: 'USPTO', receipt: 'receipt', next: () => 'file' },

Read them in context: The rules table, Trademark response.

npx services.dev check --json · the rule_unread failure
{
  "code": "rule_unread",
  "reason": "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.",
  "next": "Rename it to what it means, or take it out. rules.services['translation'].notarize is a row no step of translation performs. A row binds nothing until a step does its act.",
  "doc_url": "https://services.dev/docs/reference/check/rule_unread",
  "file": "rules.ts",
  "line": 8,
  "path": "rules.services['translation'].notarize"
}
The whole document check --json prints
npx services.dev check --json
{
  "ai": {
    "cents_per_call": 42,
    "simulated": true
  },
  "mcp": {
    "tools": 13,
    "tokens": 4490,
    "sandbox": 9,
    "budget": 8000
  },
  "files": [
    {
      "path": "rules.ts",
      "lines": 7,
      "kind": "rules",
      "says": "rules 2026-10 · 1 Service · 2 rows"
    }
  ],
  "services": [