File it somewhere

The running jobjob_7Hq2 · accepted · document.fr-CA.pdf · $49.00 final

Send the signed translation to a registry, keep its receipt, route a coded rejection back to the signer, and charge on the receipt.

Often the job is done only when someone else's system accepts it. A submit step sends the work there, keeps every receipt, routes each rejection by its code, and holds the charge until the system accepts. This is Grow 4: Grow 3's sworn translator still signs, and what they signed is what's filed.

check counts 22 lines. The Pool is Grow 3's, and the table gains one row:

grow/submit.ts
import { Pool, Rules, Service } from 'services.dev'

// Grow 4, file it somewhere: once the sworn translator signs, the translation goes to a registry, and its
// receipt fills `filing` and releases the charge. A rejection coded missing_certificate goes back to the
// signer; any other code, to the AI, and the changed file is signed again. Two rejections, or silence, hand
// the filing to the buyer.
export default Service({
  name: 'Translation',
  description: 'A document, translated, signed and filed.',
  input: { document: 'file', to: 'language: a BCP 47 tag, such as fr-CA' },
  output: { document: 'file', filing: 'text' },
  rules: rules(),
  roles: { translator: { pool: translators(), pay: 30 } },
  deliver: {
    glossary: { ask: 'buyer', to: 'answer', items: () => [{ id: 'terms', label: 'Which terms stay in English?' }] },
    translate: 'ai',
    harden: { review: 'adversarial', lenses: ['meaning', 'terms'], rounds: 2 },
    sign: { by: 'translator', stamp: true, est: '20min' },
    file: {
      submit: 'https://registry.example.test/filings',

file sends the signed document to the registry and writes its receipt into filing. rejected routes by code: missing_certificate back to sign, anything else back to translate, so a changed file is reviewed and signed again. Filing is an act, so it needs a row; without one, check refuses with act_unruled.

KeyWhat it doesDefault
submitThe outside system: a URL, or your function that sends one submission and returns the answerrequired
toIts name on the recordrequired
receiptThe output field the receipt fillsrequired
actThe act this step performs, as your rules table names itthe step's name
rejectedWhere a rejection goes: one step, a map from codes to steps with '*' for the rest (the order you write the codes in is their priority), or a function of the codesthe fallback
perfectA repair resubmitted inside this window keeps the first submission's timeoff
waitHow long an answer may take; silence past it goes to the fallback2d
fallbackWhat runs after two rejections, or silencenone: the job stops at $0
eachOne submission and receipt per element of a list; lead goes firstoff

On localhost a simulated registry answers example.test. Sending to outside systems covers every key.

Run
npx services.dev check
magic values: metadata.sandbox.outside = { reject: [missing_certificate] }
services/translation.ts · 22 lines · $89 per job, within 48 hours, charged on file's receipt.
  who     A translator signs every document before it is filed. (rules 2026-10) The buyer answers at glossary.
  roles   translator · sworn translator · from sworn-translators · $30 an act
  steps   glossary · buyer · answer → translate · ai → harden · ai · adversarial · 2 lenses → sign · translator · * · stamped → file · the registry → hand_off · buyer · answer · routed
  bounds  reserved sign to sworn translator · open file
  agent   order_translation · 27 of 30 words · job_7Hq2 waits on party
  ledger  +$89.00 · AI $1.26 (example) · translator $30.00 · you $57.74
  margin  at least $41.20 when the first translator signs ($89.00 − $30.00 translator − $17.80 AI cap) · $26.20 at the ladder's cap, the lower of 50% and what the floor leaves · $11.20 after a decline and a second translator
          a job that stops costs you at most $47.80
  apps    customer portal · delivery portal · owner console

mcp     13 buyer tools · 4,506 of 8,000 tokens · 9 more with ?toolset=sandbox
✓ ready to sell · 1 Service · it keeps at least 46% of every job
  next    npx services.dev dev

AI and money are simulated until you wire them. $0.42 a call is an example.

The price now ends charged on file's receipt: the buyer's money stays held until the registry accepts. price.release names another step.

The magic value metadata.sandbox.outside = { reject: [missing_certificate] } makes the simulated registry reject the first filing:

npx services.dev translation document=https://example.com/lease.pdf to=fr-CA --max 89 --key grow --metadata 'sandbox.outside={"reject":["missing_certificate"]}'
job_7Hq2 · working · $89.00 held
job_7Hq2 · waiting · on party · $89.00 held
  record  
  next    hand http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ to the person it asks

# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}

$ npx services.dev jobs wait job_7Hq2
job_7Hq2 · waiting · on claim · $89.00 held
  record  glossary.terms · answered | glossary · buyer · answer · clock paused 2h | translate · ai | harden · ai · adversarial · 2 lenses · pass
  next    a member claims tsk_7Hq2: npx services.dev tasks sheet tsk_7Hq2, in their session

# mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim · POST /tasks/tsk_7Hq2/clear · POST /tasks/tsk_7Hq2/attest

$ npx services.dev jobs wait job_7Hq2
job_7Hq2 · waiting · on claim · $89.00 held
  record  glossary.terms · answered | glossary · buyer · answer · clock paused 2h | translate · ai | harden · ai · adversarial · 2 lenses · pass | sign · person · sworn translator · * · mbr_7Hq2 | file · the registry · rejected · missing_certificate · repairing at sign
  next    a member claims tsk_7Hq3: npx services.dev tasks sheet tsk_7Hq3, in their session
curl -X POST http://localhost:8787/jobs -H 'content-type: application/json' -d '{"service":"translation","document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":89,"metadata":{"sandbox":{"outside":{"reject":["missing_certificate"]}}}}'
  next    get_job {"id":"job_7Hq2"}

$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← 200 · job_7Hq2 · waiting · on party · $89.00 held
  next    hand_to_person {"ask":"ask_7Hq2"}

# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}

$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← 200 · job_7Hq2 · waiting · on claim · $89.00 held
  next    get_job {"id":"job_7Hq2"}

# mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim · POST /tasks/tsk_7Hq2/clear · POST /tasks/tsk_7Hq2/attest

$ curl http://localhost:8787/jobs/job_7Hq2?wait=50
← 200 · job_7Hq2 · waiting · on claim · $89.00 held
  next    get_job {"id":"job_7Hq2"}

→ tools/call order_translation {"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":89,"metadata":{"sandbox":{"outside":{"reject":["missing_certificate"]}}}}
← job_7Hq2 · working · $89.00 held
  next    get_job {"id":"job_7Hq2"}

→ resources/read job://job_7Hq2?wait=50
← job_7Hq2 · waiting · on party · $89.00 held
  next    hand_to_person {"ask":"ask_7Hq2"}

# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}

→ resources/read job://job_7Hq2?wait=50
← job_7Hq2 · waiting · on claim · $89.00 held
  next    get_job {"id":"job_7Hq2"}

# mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim · POST /tasks/tsk_7Hq2/clear · POST /tasks/tsk_7Hq2/attest

→ resources/read job://job_7Hq2?wait=50
← job_7Hq2 · waiting · on claim · $89.00 held
  next    get_job {"id":"job_7Hq2"}
await lingua.translation.order({"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","key":"grow","max_usd":89,"metadata":{"sandbox":{"outside":{"reject":["missing_certificate"]}}}})
← job_7Hq2 · working · $89.00 held
  next    get_job {"id":"job_7Hq2"}

await lingua.jobs.wait("job_7Hq2")
← job_7Hq2 · waiting · on party · $89.00 held
  next    hand_to_person {"ask":"ask_7Hq2"}

# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}

await lingua.jobs.wait("job_7Hq2")
← job_7Hq2 · waiting · on claim · $89.00 held
  next    get_job {"id":"job_7Hq2"}

# mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim · POST /tasks/tsk_7Hq2/clear · POST /tasks/tsk_7Hq2/attest

await lingua.jobs.wait("job_7Hq2")
← job_7Hq2 · waiting · on claim · $89.00 held
  next    get_job {"id":"job_7Hq2"}

The record:

Call
→ POST /translation {"document":"lease.pdf","to":"fr-CA","key":"grow","max_usd":89,"metadata":{"sandbox":{"outside":{"reject":["missing_certificate"]}}}}
magic values: metadata.sandbox.outside = { reject: [missing_certificate] }
← job_7Hq2 · waiting · on party · $89.00 held
  waiting {"on":"party","since_at":"2026-10-25T14:02:11.000Z","step":"glossary","clock":"paused"}
# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}

← job_7Hq2 · waiting · on claim · $89.00 held
  waiting {"on":"claim","since_at":"2026-10-25T16:02:11.000Z","step":"sign","task":"tsk_7Hq2"}
# on localhost you are the owner, and the owner holds no credential
→ POST /sworn-translators/jobs/job_7Hq2/approve
← 403 needs_a_person: sign is tsk_7Hq2, a Task for a member of sworn-translators: the member who claims and clears it attests, in their own session.

# 20 minutes later, mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim
← tsk_7Hq2 · claimed · translator · sign · * · $30.00 · 20 min · $90.00 an hour · due 2026-10-27 16:02
→ POST /tasks/tsk_7Hq2/clear · GET /tasks/tsk_7Hq2/packet
→ POST /tasks/tsk_7Hq2/attest
← tsk_7Hq2 · done · translator · sign · * · $30.00 · attested · 20 min · $90.00 an hour · due 2026-10-27 16:02

← job_7Hq2 · waiting · on claim · $89.00 held
  waiting {"on":"claim","since_at":"2026-10-25T16:22:11.000Z","step":"sign","task":"tsk_7Hq3"}

missing_certificate sent the job back to sign: two signatures at $30, so you kept $27.74, above the $11.20 worst case check printed. The charge released on rcp_7Hq3, sent at 16:42 and dated 16:22, the first filing's time, because the repair landed inside perfect.

metadata.sandbox.outside = reject_once rejects with a code the file doesn't name, so the work goes back to translate:

Call
→ POST /translation {"document":"lease.pdf","to":"fr-CA","key":"grow","max_usd":89,"metadata":{"sandbox":{"outside":"reject_once"}}}
magic values: metadata.sandbox.outside = reject_once
← job_7Hq2 · waiting · on party · $89.00 held
  waiting {"on":"party","since_at":"2026-10-25T14:02:11.000Z","step":"glossary","clock":"paused"}
# 2 hours later, the buyer answers through the link
→ POST /asks/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ {"items":{"terms":{"answer":"Lessee, Lessor, Premises"}}}

← job_7Hq2 · waiting · on claim · $89.00 held
  waiting {"on":"claim","since_at":"2026-10-25T16:02:11.000Z","step":"sign","task":"tsk_7Hq2"}
# on localhost you are the owner, and the owner holds no credential
→ POST /sworn-translators/jobs/job_7Hq2/approve
← 403 needs_a_person: sign is tsk_7Hq2, a Task for a member of sworn-translators: the member who claims and clears it attests, in their own session.

# 20 minutes later, mbr_7Hq2, the sworn translator, claims tsk_7Hq2 in the delivery portal and signs, in their own session
→ POST /tasks/tsk_7Hq2/claim
← tsk_7Hq2 · claimed · translator · sign · * · $30.00 · 20 min · $90.00 an hour · due 2026-10-27 16:02
→ POST /tasks/tsk_7Hq2/clear · GET /tasks/tsk_7Hq2/packet
→ POST /tasks/tsk_7Hq2/attest
← tsk_7Hq2 · done · translator · sign · * · $30.00 · attested · 20 min · $90.00 an hour · due 2026-10-27 16:02

← job_7Hq2 · waiting · on claim · $89.00 held
  waiting {"on":"claim","since_at":"2026-10-25T16:22:11.000Z","step":"sign","task":"tsk_7Hq3"}

The redo changed the document, so the gate refused the old signature (unattested) and posted a second Task. You kept $26.48.

Run
npx services.dev diff grow/role.ts grow/submit.ts
magic values: metadata.sandbox.outside = { reject: [missing_certificate] }
grow/role.ts (13 lines) → grow/submit.ts (22 lines)
Translation · changed description, output, deliver, rules, price · adds a submit step: an outside system and its receipts

API   ~ operation service.order.translation  POST /translation  item.description, item.responses.200.content.application/json.schema.properties.output.properties.filing, item.responses.200.content.application/json.schema.properties.output.required  · breaking
      ~ operation service.quote.translation  GET /translation.json  answers.description, answers.output.properties.filing, answers.output.required  · breaking
      ~ operation brand.manifest             GET /manifest.json  answers.services
      + object rcp_                          receipt
      + 4 events                             submission.sent, receipt.accepted, receipt.rejected, receipt.timed_out
CLI   ~ operation service.order.translation  services.dev translation document=… to=…  price
      + 4 events                             submission.sent, receipt.accepted, receipt.rejected, receipt.timed_out
MCP   ~ operation order_translation          order_translation  description, outputSchema.oneOf
      ~ prompt buy_translation               prompts/get buy_translation  description
SDK   ~ object job_ (translation)            Job<"translation">  + output: { document: File; filing: string }, + stage: "glossary" | "translate" | "harden" | "sign" | "file" | "hand_off"
      + object rcp_                          receipt
      + 4 events                             submission.sent, receipt.accepted, receipt.rejected, receipt.timed_out
Site  ~ operation service.order.translation  /translation  + A document, translated, signed and filed., + $89 per job, within 48 hours, charged on file's receipt. A translator signs at sign, stamp
      ~ operation service.quote.translation  /translation.json  description, output.properties.filing, output.required  · breaking
Docs  ~ operation service.order.translation  /llms.txt · Translation  + A document, translated, signed and filed. $89 per job, within 48 hours, charged on file's 
App   ~ operation ask.complete               /a/:id · hosted page  + translation: hand_off · buyer answer · routed
      ~ operation job.ledger                 /console · owner console  + translation: $89 per job, within 48 hours, charged on file's receipt. A translator signs a

The output gains filing, breaking for clients that type it, and the receipt object and four events arrive. Grow 3's Pool and Tasks carry over.

In a brand, the tax example files once per jurisdiction with each and routes each rejection to the signature it needs.

A date the world sets gives the filing a date. All four, in one filing adds a signature. How a job runs puts every step on one map.