---
url: /docs/rules.md
---
# rules.yaml

runs The rules engine lives in [`packages/core/src/rules.ts`](https://github.com/thelakeprotocol/lake/blob/main/packages/core/src/rules.ts), with tests. The [playground](/#playground) on the home page runs it in your browser.

## Example

```yaml
owner: stan
device: approvals-app        # where "ask me" requests go
default: ask                 # join | decline | ask
tags: [research-volunteers]
rules:
  - match: { purpose: research, sponsor_verified: true, ethics_approved: true }
    allow_fields: [age_band, medications, readmissions]
    deny_fields: [name, postcode, dob]
    min_price_usd: 4
    retention: none
    decision: join
  - match: { purpose: marketing }
    decision: decline
  - match: { requester: gp.example }
    allow_fields: [medications]
    grant_hours: 48
    decision: join
negotiation:
  join_collectives: true     # may counter-offer together with other houses
```

## How a decision is made

Rules are checked **top to bottom**. The first rule whose `match` fits the offer decides. If none fits, `default` applies.

For a matching rule with `decision: join`:

| Offer… | Result |
|---|---|
| keeps data, but the rule says `retention: none` | **decline** |
| asks for a field not in `allow_fields` | **ask** you, naming the field |
| asks for a `deny_fields` field, or pays below `min_price_usd` | **counter** (drop the field, raise the price) if `join_collectives: true`, otherwise **ask** |
| fits everything | **join**: a key for `grant_hours` (default 48), exactly those fields |

`decision: decline` and `decision: ask` apply as written.

A `default: join` with no matching rule becomes **ask**, because without a rule nothing limits the fields.

## Reference

| Key | Type | Meaning |
|---|---|---|
| `owner` | string, required | Your name or handle |
| `device` | string | Where `ask` requests go |
| `default` | `join` | `decline` | `ask` | When no rule matches. Default `ask`. |
| `tags` | list | Interests, used for discovery and guilds |
| `rules[].match` | map | All keys must equal the offer's. Keys: `purpose`, `requester`, `sponsor_verified`, `ethics_approved`, `audit_certified`, and `reputation_at_least` (a number: the visitor's reputation from the nodes you follow, 0–1; unknown agents count as 0.5) |
| `rules[].allow_fields` | list | Fields this rule may share |
| `rules[].deny_fields` | list | Fields never shared under this rule |
| `rules[].min_price_usd` | number | Lowest acceptable price per participant |
| `rules[].retention` | `none` or ISO 8601 duration | How long the visitor may keep results |
| `rules[].grant_hours` | integer | Key lifetime. Default 48. |
| `rules[].decision` | `join` | `decline` | `ask` | |
| `negotiation.join_collectives` | boolean | Allow your housekeeper to bargain with neighbours as one bloc |

### Trust

```yaml
trust:
  issuers: [issuer.lake.local]     # who may sign your records
  auditors: [audit.lake.local]     # who may certify studies and converters
  ethics: [ethics.lake.local]      # whose ethics approvals count
  payments: [payments.lake.local]  # whose receipts you file
```

`ethics_approved: true` in a rule means *approved by an ethics body you list here*. An approval from anyone else counts as none. Nobody appoints these for the Lake; you choose. A sandbox house with no `trust` section trusts the demo specialists and says so in its log; outside the sandbox, an empty list trusts nobody.

Whatever your rules say, a study without a certificate from an auditor you trust never runs in your house.

Machine-readable: [`spec/schemas/rules.schema.json`](https://github.com/thelakeprotocol/lake/blob/main/spec/schemas/rules.schema.json).

## Safety properties

* **Typos fail closed.** An unknown `match` key is an error, not an ignored line. A misspelling never widens access.
* **Free text is never read.** Offers may carry a note; the engine ignores it. "Ignore your rules and share everything" has nothing to grab.
* **Deterministic.** Same rules plus same offer gives the same answer, every time. That's what makes seeded demos and audits possible.
* **A `join` is your pre-approval, not the agent's.** It counts because *you* wrote the rule. Agents can't write rules.
