---
title: "Rules: how a Service meets licensing by design"
url: https://services.dev/docs/people/rules
captured: [gate-reserved-act-unstaffed.txt, legal-boundary.txt, member-acts.txt]
codes: [act_refused, brand_unverified, region_not_ratified, reserved]
---
# Rules: how a Service meets licensing by design

URL: https://services.dev/docs/people/rules

> One table your counsel writes, with a row per act. Each act is reserved to a credential, open to the brand, refused here, or the client's own decision, and the gate holds every job to the version it was ordered under.

A rules table answers one question: which acts in this work need a credentialed person, which does the brand perform itself, and which are never performed here?

`Rules()` is how a Service meets licensing by design. Your counsel writes one table, with rows per Service. The AI still does the work. The table decides which acts a credentialed person performs, and the gate holds every job to it, the same way for AI and people. services.dev ships no rules: every position in the table is your brand's.

## The smallest form

One row, and the Service that names the table:

```bash
cat rules.ts
```

```txt title="magic values: none"
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.' },
    },
  },
})
```

`check` reads the row back on the Service it binds:

```bash
npx services.dev check
```

```txt title="magic values: none"
rules.ts · 6 lines · rules 2026-10 · 1 Service · 1 row
  who     A translator signs every document before delivery. (rules 2026-10)
  bounds  reserved sign to sworn translator
```

## Four kinds of row

Each row maps an act, by the name a step performs, to one of four kinds, and a reserved row may name a credential the brand holds instead of a person's. As `check` reads them, from the legal example and [the filing brand](https://services.dev/docs/people/brand-credentials.md):

| Row | What it means | `check` reads |
|---|---|---|
| `{ reserved: 'bar admission', where: 'client' }` | A person who holds the credential performs it, in the region the job's facts give | reserved sign to bar admission where the client is |
| `{ reserved: { brand: 'efin' } }` | The brand performs it, under a credential it holds | reserved file to the brand's efin |
| `{ open: true }` | The brand performs it directly | open send and courier |
| `{ refused: true, route }` | Never performed here. An order for it gets the route | refused litigate |
| `{ client: true }` | The client's decision alone | the client decides settle, alone |

```bash
npx services.dev check   # each Service's bounds line
```

```txt title="magic values: none"
services/demand-letter.ts
  bounds  reserved sign to bar admission where the client is · the attorney sells the fee · open send and courier · the client decides settle, alone · refused litigate
```

Every row records its `position`: the reason counsel gives for it. The Service page prints it beside the act, so a buyer reads the boundary before they order:

```bash
open http://localhost:8787/contract-review
```

```txt title="magic values: none"
The boundary
  advise: reserved to bar admission where the client is, in US-CA, US-NY and US-TX only · Advice on a contract is legal advice in the client's state.
  send: the brand performs it · Sending a signed memo to its client is a delivery act.
  litigate: never performed here · Litigation is retained directly, never ordered here.
```

## No fallback

There is no global default. A step that performs an act with no row, under its Service or `shared`, fails [`act_unruled`](https://services.dev/docs/reference/check/act_unruled.md), and a reserved act no role can perform fails [`reserved_act_unstaffed`](https://services.dev/docs/reference/check/reserved_act_unstaffed.md). A Service can't ship with an act the table doesn't decide.

## Versions, and why config can't remove a reservation

The table is deep-frozen. New positions are a new `version`. A job pins the version it was ordered under, and every attestation on its record names it:

```txt
→ POST /reviews/tsk_7Hq2/attest {"checklist":["the claims read on the disclosure","every inventor is named","a reasonable inquiry was made"]}   # as mbr_7Hq2
```

```txt title="magic values: applicant.email = signs@example.test, client.email = signs@example.test, member = mbr_eligible"
  14:02 · step.committed · sign · person · patent agent · * · mbr_7Hq2
  attested {"member":"mbr_7Hq2","credential":"patent agent","region":"*","hash":"sha256:27d7bdec3be22fd633af279053beef779c9c5080629816867bbfc4e4593a5b33","rules":"2026-10"}
```

Product config can't edit the table, and neither can policy, a reseller or an escalation. Only the brand's next version changes a position, and only for jobs ordered under it. A reserved act commits only with the attestation of a person who holds the credential, under the pinned version: that is the gate's authority check, in [Propose, then the gate commits](https://services.dev/docs/service/gate.md).

## Every form

| Key | What it says | Default |
|---|---|---|
| `version` | The table's version. A job pins the one it was ordered under | required |
| `services.<id>.<act>` | A row for one act of one Service | none: no fallback |
| `shared.<act>` | A row every Service that names the act shares | none |
| `position` | Counsel's reason, printed on every surface that shows the row | required |
| `reserved` | A credential expression, or `{ brand }` | — |
| `and` | A credential the brand holds as well: `{ brand: 'efin' }` | none |
| `where` | Where the act happens: `'client'`, `'governing'`, `'recipient'`, `'*'` or your function | `'*'` |
| `regions` | Where the row is ratified, in ISO 3166 | every region |
| `kind` | `'advice'` reserves a class of answer in the job's thread, apart from a signature | `'act'` |
| `rail` | Who sells the fee for the act's role: `'brand'`, `'member'` or `'entity'` | `'brand'` |
| `self` | The client may perform the act themselves | off |
| `flags` | Advisory signals raised when facts straddle regions. They never block | none |
| `open` | The brand performs the act | — |
| `refused`, `route` | Never performed here, and where it is: a URL, or a Service of yours that does it | — |
| `client` | The client's decision alone | — |
| `cite` | The authorities counsel relies on, printed after the row on every refusal that cites it | none |

[Where an act happens](https://services.dev/docs/people/where.md) has `where`, `regions` and `flags`, [Talk](https://services.dev/docs/service/talk.md) has `kind: 'advice'`, and [When the client does it themselves](https://services.dev/docs/people/self.md) has `self`.

## Seven surfaces

| Surface | What a row changes |
|---|---|
| API | An order for a refused act, or in a region a row doesn't ratify, answers 403 and charges nothing |
| CLI | `check` prints each Service's `bounds` line, and the CLI refuses as the API does |
| MCP | A tool call for a refused act answers with the route to hand on |
| SDK | The API's answer, unchanged |
| Site | The Service page prints the boundary, each act with its position |
| Docs | Each refusal's code has a page on the brand's docs, at the `doc_url` the refusal carries |
| App | Every attestation on the record each app reads names the version |

## Refusals

| Code | When | Charged |
|---|---|---|
| [`act_refused`](https://services.dev/docs/reference/errors.md#act_refused) | An order names an act a row refuses | nothing |
| [`region_not_ratified`](https://services.dev/docs/reference/errors.md#region_not_ratified) | The job's region is outside a row's `regions` | nothing |
| [`brand_unverified`](https://services.dev/docs/reference/errors.md#brand_unverified) | A brand credential the row needs is unverified or stale | nothing |
| [`reserved`](https://services.dev/docs/reference/errors.md#reserved) | The person who attested doesn't hold the credential | nothing |
| [`act_unruled`](https://services.dev/docs/reference/check/act_unruled.md) | `check`: a step's act has no row | — |
| [`reserved_act_unstaffed`](https://services.dev/docs/reference/check/reserved_act_unstaffed.md) | `check`: no role holds the reserved credential | — |
| [`rail_conflict`](https://services.dev/docs/reference/check/rail_conflict.md) | `check`: a role's `pay.rail` contradicts its row's | — |
| [`brand_credential_missing`](https://services.dev/docs/reference/check/brand_credential_missing.md) | `check`: a row names a brand credential `Brand()` doesn't declare | — |
| [`row_unpositioned`](https://services.dev/docs/reference/definition-errors.md#row_unpositioned) | the file won't load: `Rules()` refuses a row with no position | — |
| [`row_kind`](https://services.dev/docs/reference/definition-errors.md#row_kind) | the file won't load: `Rules()` refuses a row that isn't exactly one of reserved, open, refused or the client's | — |

[Refusals and cures](https://services.dev/docs/people/refusals.md) has what each answer carries.

## Three brands

The rows in the examples are illustrative. Your counsel writes your brand's. For the legal side, [law.org.ai](https://law.org.ai) collects positions by act and region.

- **Legal.** Five Services in one table: advice reserved where the client is, signing and filing reserved to a practitioner, litigation refused. [The rules table](https://services.dev/docs/examples/legal/rules.md).
- **Tax.** An e-filing reserved to the brand's own e-file number, `{ reserved: { brand: 'efin' } }`. [Credentials the brand holds](https://services.dev/docs/people/brand-credentials.md).
- **Sales.** Its setters and closers hold no credential, so no row reserves their acts. A sales brand that needs one adds a row, the same way.

---

- Up: [human: the line](https://services.dev/docs/people.md)
- Previous: [human: the line](https://services.dev/docs/people.md)
- Next: [Roles](https://services.dev/docs/people/roles.md)
