Propose, then the gate commits
AI, your code, a person and an outside system all propose. One gate runs seven checks in order and commits or refuses, the same way for everyone.
The gate answers one question: may this output become part of the job?
No step writes to a job directly. Every doer hands the gate a proposal: the AI, your code, a URL, a nested Service, a person and an outside system's answer. The gate is the only code that writes a step's output to the record. On a pass it writes step.committed. On a refusal it writes step.refused with a code, and the doer's work stays open.
| # | Check | Holds when | On the record | A person gets | Explained in |
|---|---|---|---|---|---|
| 1 | terminal | The job is not at an end. | job_ended | job_ended | The job lifecycle |
| 2 | direction | No decline awaits the client's direction. | needs_direction | needs_direction | Declines and direction |
| 3 | graph | The graph allows the step, and any grant it needs is live. | step_not_ready, needs_grant | not_waiting, needs_grant | The step graph |
| 4 | mechanics | Every policy value within its bounds. A client's decide is never undone here (5b). | policy_invalid | policy_invalid | Policy |
| 5 | merits | Merits come only from the client, and the guard checks the output against them. | merits_not_yours, guard_refused | merits_not_yours, guard_refused | Guards |
| 6 | authority | The rules row, never the sign line, under the job's pinned version. A reserved act commits only from a holder of its credential, in the region it derives and where the row is ratified, with every brand credential it names verified. It carries its attestation, every checklist item affirmed, over the output as it stands now. | unattested, reserved, region_not_ratified, brand_unverified | needs_a_person, reserved, region_not_ratified, brand_unverified | Rules |
| 7 | proof | An end that charges has its release fact on the record. | release_unproven | end_not_allowed | How a job ends |
Any check can refuse, and the first that does decides the answer.
1. Terminal. An end is forever. Ending the job again answers end_not_allowed, and any other act on it answers job_ended:
→ POST /jobs/job_7Hq2/end {"end":"withdrawn"} "code": "end_not_allowed",
"reason": "The job already ended at withdrawn, and an end is forever.",→ POST /jobs/job_7Hq2/messages {"text":"Can you still change page 2?"} "code": "job_ended",
"reason": "The job has ended, for good.",2. Direction. A member's decline holds the job until the client directs it, to another member or to an end. While it waits, nothing else moves the job, from your console or the buyer's key:
→ POST /owner/matters/job_7Hq2/approve # the owner console← 409 needs_direction: A signer declined, and only the client's direction moves the matter on.Declines and direction walks the decline, the client's link and the member they name.
3. The graph. A step runs only when every step and ask it follows is on the record. Translation waits for the buyer's glossary before it starts:
→ POST /translation {"document":"lease.pdf","to":"fr-CA","max_usd":49,"key":"glossary"}← job_7Hq2 · waiting · on party · $49.00 held
waiting {"on":"party","since_at":"2026-10-25T14:02:11.000Z","step":"glossary","clock":"paused"}4. Mechanics. Every policy value sits inside its bounds. An order that breaks them hears every violation at once:
→ POST /checked-translation {"document":"manual.pdf","to":"de","policy":{"match":1.5,"turnaround":"fast"},"max_usd":79,"key":"bad"} "code": "policy_invalid",
"reason": "The policy breaks 2 bounds: match must be at most 1; turnaround must be a number.",5a. Merits. Merits come only from the client, and a step's guard checks the output against them. The brand can't set one, and an output below one is refused, whoever proposed it:
→ POST /owner/jobs/job_7Hq2/policy {"policy":{"match":0.7}} "code": "merits_not_yours",
"reason": "match binds the merits, and only the client sets them.",5b. A client's committed decision is never undone on mechanics.
6. Authority. The rules row the job pinned decides, never the sign line. Where the row isn't ratified, the order is refused at $0:
→ POST /matters {"service":"contract-review","contract":{"url":"data:text/plain,A%20lease.","name":"lease.pdf"},"client":{"name":"Ada Lovelace","email":"ada@example.com"},"client_region":"US-FL","max_usd":99,"key":"lease-fl"} "code": "region_not_ratified",
"reason": "advise is ratified in US-CA, US-NY, US-TX under rules 2026-10, and this order's region is US-FL.",So is an act under a brand credential that isn't verified and fresh:
→ POST /jobs {"service":"extension","year":2025,"max_usd":25,"key":"stale","metadata":{"sandbox":{"brand":"stale"}}} "code": "brand_unverified",
"reason": "file is performed under the brand's efin, which isn't verified and fresh.",7. Proof. An end that charges has its release fact on the record. With no receipt, the filing that fell back to the buyer charges nothing when they accept it:
→ POST /jobs/job_7Hq2/accept← job_7Hq2 · accepted · lease.fr-CA.pdf · $0
ledger AI $0.84 · you −$0.84The AI's refused proposal is redone inside its budget. A person's gets 422 guard_refused, and the step stays theirs:
→ POST /proofreaders/jobs/job_7Hq2/approve {"output":{"match":0.8}} "code": "guard_refused",
"reason": "match: below the match the client set (0.8 < 0.9)",The gate holds both: a member's decline at check 2, and the client's yes on their merits at 5a and 5b. Two sovereigns explains why neither can be overridden.
| Op | API | CLI | MCP | SDK | Site | Docs | App |
|---|---|---|---|---|---|---|---|
job.approve | POST /:portal/jobs/:id/approve, POST /jobs/:id/approve, POST /jobs/:id/sign, POST /owner/jobs/:id/approve | jobs approve <job> | never: a person acts | never: a person acts | 8 error pages | owner console, delivery portal |
Events: step.refused, job.paused.
Operations has every column of every row.
| Code | Category | HTTP | Why |
|---|---|---|---|
job_ended | conflict | 409 | The job has ended, for good. |
needs_direction | conflict | 409 | A member declined, and only the client's direction moves the job on: another member, or an end. |
not_waiting | conflict | 409 | The job isn't waiting on a person. |
needs_grant | blocked | 403 | A grant this step needs isn't live: not given yet, expired or revoked. |
policy_invalid | invalid_request | 422 | A policy value is out of bounds; the reason lists each. |