Verbs
A Verb is something people or agents do to a Noun. Lease has two: renew moves the end date forward, and end closes the lease. A Verb declares what it does, the input it takes and the states it moves between, and each Verb becomes a button, an endpoint, a command and a tool.
---
name: Lease
fields:
unit: { type: link, target: Unit, required: true }
renter: { type: email, required: true }
endsOn: { type: date, required: true, indexed: true }
status: { type: choice, options: [{ key: active }, { key: ended }] }
verbs:
renew:
does: Move the end date forward
input: { endsOn: { says: the new end date, type: date, required: true } }
from: [active]
end: { does: End the lease, from: [active], to: ended }
---Renew a lease
Northgate Rentals runs its 140 units on Leaseclock. Dana Whitfield's lease on Unit 4B ends on November 30, 2026, and Priya Shah, the leasing coordinator, renews it through November 30, 2027. She can do that from any surface. Each one runs the same renew, takes the same input, checks the same state and returns the same lease.
At leaseclock.com/app/leases, every active lease has a Renew button. Priya opens it on 4B, and the popover asks for one thing: The new end date. It shows Dana's current end date beside the field, so Priya sees the change before she presses Renew. The lease page then records who renewed it and when, with Undo beside the entry.
Name
The key under verbs: names the Verb. Write it in camelCase, starting lower-case, as something a person does: renew, end, markPaid. The name sets every place the Verb appears:
| Surface | renew on Lease becomes |
|---|---|
| App | A Renew button on each lease (markPaid becomes Mark paid) |
| API | POST /api/leases/{id}/renew |
| CLI | npx leaseclock do Lease.renew <id> |
| SDK | $.Lease.renew(id, input) |
| MCP | "verb": "Lease.renew" in the do tool |
| Docs | leaseclock.com/docs/lease#renew |
Push a new Verb and every surface has it on the next request: the button, the endpoint, the command, the method, the docs section and a changelog entry.
What it does
does is one sentence, written as an instruction to the person about to run the Verb. Leaseclock shows it as the hint on the Renew button, the summary at the top of the Verb's docs section, the description in the OpenAPI file and the help text in the CLI. Agents read it too, which is how they pick the right Verb.
$ npx leaseclock do Lease.renew --help
Move the end date forward
Usage: npx leaseclock do Lease.renew <id> --endsOn <date>
--endsOn date required The new end date
Runs from: activeInput
input lists what the Verb needs from whoever runs it. Each input is a field, with the same types and options as a Noun's fields, plus says, the label a person reads.
| Line | What it generates |
|---|---|
endsOn: | The input's key in the request body, the SDK type and the tool's input schema, and the --endsOn flag |
says: the new end date | The popover's label, The new end date, and the description in the docs and the CLI help |
type: date | A date picker in the popover, and a check that rejects anything that is not a date |
required: true | A required mark in the popover, and a 422 that names endsOn when it is missing |
A Verb with no input runs on one click. end takes none, so its button asks only for confirmation.
States
from lists the states a Verb runs from, and to names the state it leaves the record in. Both name options of the Noun's status field. end runs from active and moves the lease to ended. renew has no to, so the lease stays active with its new end date. A Verb with no from runs in any state.
On a lease in any other state, the Verb does not run, and every surface says why in the same words. Hannah Cho's lease on 5A ended on September 30, 2026, so its Renew and End buttons are disabled with the reason beside them, and the API answers 409:
$ npx leaseclock do Lease.end 5a
✗ Lease 5A is ended. end runs from active.The states also drive the list: each status option gets a tab, and a lease's badge changes the moment a Verb moves it.
Logic
A Verb with no fn writes its input onto the record. renew declares endsOn as its input, so it sets endsOn and touches nothing else, and end, which takes no input, only moves the lease to ended. Most Verbs need nothing more.
When a Verb needs logic of its own, export a function with the Verb's name after the front matter of the Noun's file. It receives the record and the input, and returns the fields to change. Throw to stop the Verb, and the message reaches whoever ran it, in the popover, the API error, the CLI or the agent's tool result.
export const renew = async (lease, { endsOn }) => {
if (endsOn <= lease.endsOn) throw new Error('endsOn must be later')
return { endsOn }
}The function runs inside SaaS.Dev with the same permissions, preview, undo and history as every other Verb. You maintain that one function. Defaults and overrides covers the rest of what you can replace.
Preview, undo and history
Every Verb comes with these, from the first push:
- Preview. Each surface shows the change before it commits. The popover shows the current and new values, the CLI asks before it runs, and on the API a GET on the Verb's URL returns the preview and changes nothing.
- Undo. Each run saves a version of the record. The API returns an
undolink, the CLI prints the undo command, and the lease page has an Undo button. - History. The lease page lists every run: which Verb, who ran it, when, and the values before and after.
- Permissions. Roles decide who may run each Verb, and every surface checks the same rule. A customer's team sees only its own workspace's leases.
- Retries. A POST with an
Idempotency-Keythat was already used returns the first result and runs nothing again.
Rules
A Verb's name is camelCase and starts lower-case. Every state in from and to is an option of the Noun's status field. Input keys follow the same rules as field keys. When a file breaks a rule, the push returns the line and the fix, so you or your agent correct it in one edit:
422 Lease.mdx line 12: renew runs from "Active", which is not a status of Lease. Use "active".Constraints explains why each rule exists, and Errors lists every code with its fix.