The attorneys' Pool

The running jobjob_7Hq2 · waiting · on claim · $149.00 held · sign · tsk_7Hq2

One Pool for the legal brand's six Services. Attorneys and patent practitioners join, the verifier checks each credential, Tasks are offered one member at a time, conflicts stay private, and tiers come from outcomes.

legal's wordsmatter is a jobasset is a subjectreview is a tasksigner is a membernetwork is a pooloutcome is a end

One Pool serves all six Services. Attorneys and patent practitioners join on their own, and the Pool's verifier is the only writer of what they hold. Every person step posts a Task to it. A member sees a teaser that names no party, a sheet that does, and the packet only once they've cleared conflicts.

Run
cat pools/attorneys.ts
magic values: none
  roles: {
    attorney: { credential: 'bar admission' },
    practitioner: { credential: ['patent attorney', 'patent agent'], where: '*' },
    watcher: { credential: ['patent attorney', 'patent agent'], where: '*' },
    student: { credential: 'law school enrollment' },
  scopes: { 'bar admission': 'region', 'patent attorney': '*', 'patent agent': '*', 'law school enrollment': 'region' },
    name: 'text: your name as admitted', credential: ['bar admission', 'patent attorney', 'patent agent', 'law school enrollment'],
  verify: 'https://verify.example.test/credentials',
  coverage: { per_act: 1_000_000, by: 'either', usd: 4 },
  sees: { teaser: ['act', 'region', 'fee_usd', 'signals'], sheet: ['parties', 'kind', 'amount'] },
  clear: { attest: ['I checked conflicts across my full book, including my firm'], check: ['client', 'applicant', 'recipient'] },
  affinity: '30d', rank: ['fit', 'track', 'speed'],
  tiers: [{ name: 'reviewer' }, { name: 'signer', after: 20, sendbacks: '5%', on_time: '95%' }, { name: 'counsel', after: 100, sendbacks: '3%', on_time: '98%' }],

The whole file, and check after it.

KeyIn this Pool
rolesattorney holds a bar admission, practitioner and watcher a patent registration, student a law school enrollment. A Service names a role; none names student yet
scopesA bar admission and an enrollment count where they're held. A patent registration counts everywhere
joinEight fields over three steps, after the email, and resumable
verifyThe only writer of what a member holds, through its own vt_ token
coverage$1,000,000 an act. A member's own policy covers their act, and the Pool's covers one who carries none, at $4 an act on the brand's ledger
seesThe teaser shows the act, the region, the fee and the signals. The sheet adds the parties, the kind and the amount
clearThe conflicts statement a member attests, and the party inputs checked against their own ledger
affinity30 days of first refusal on follow-on work for the same asset
rankFit, then track record, then speed
tiersReviewer, then signer after 20 reviews, then counsel after 100, each with a send-back and on-time bar
left out: dispatchOne member at a time, because clear.check names parties
left out: holdThree times the step's estimate, at least a day
left out: fillThe default: raise 10% at 2 hours, widen a tier at 4, then firms at 8 and staff at 12
left out: payoutsPay accrues at the act and is payable two days after it

Joining is email first and resumable, one group of fields a step:

Call
→ POST /attorneys/join {"email":"grace@example.com"}
magic values: client.email = signs@example.test
← 200 mbr_7Hq2 · attorneys · grace@example.com · applying, step 1 of 3: name, credential, number · on
→ POST /attorneys/join {"email":"grace@example.com","answers":{"name":"Grace Hopper","credential":"bar admission","number":"CA-301"}}
← 200 mbr_7Hq2 · attorneys · grace@example.com · applying, step 2 of 3: regions, areas, hours · on
→ POST /attorneys/join {"email":"grace@example.com","answers":{"regions":["US-CA","US-NY"],"areas":["contracts","collections"],"hours":10}}
← 200 mbr_7Hq2 · attorneys · grace@example.com · applying, step 3 of 3: turnaround, min_fee · on
→ POST /attorneys/join {"email":"grace@example.com","answers":{"turnaround":"24h","min_fee":40}}
← 200 mbr_7Hq2 · attorneys · grace@example.com · verified (simulated): bar admission in US-CA, US-NY · on
  verified {"credentials":{"bar admission":["US-CA","US-NY"]},"coverage":{"per_act":1000000,"until":"2027-10-25T14:02:11.000Z"},"at":"2026-10-25T14:02:11.000Z","by":"fixture","fresh_until":"2026-11-24T14:02:11.000Z","fresh":true}

Only the verifier's own token writes verified facts. The brand's key can't:

Call
→ POST /attorneys/signers/mbr_7Hq2/verify {"facts":{"credentials":{"bar admission":["US-CA","US-NY","US-NJ"]},"coverage":{"per_act":2000000,"until":"2027-10-25T00:00:00Z"}}}   Authorization: Bearer sk_test_7Hq2
magic values: client.email = signs@example.test
← 401 key_invalid: This door takes the network's verifier token, Authorization: Bearer vt_…, and nothing else.
→ POST /attorneys/signers/mbr_7Hq2/verify {"facts":{"credentials":{"bar admission":["US-CA","US-NY","US-NJ"]},"coverage":{"per_act":2000000,"until":"2027-10-25T00:00:00Z"}}}   Authorization: Bearer vt_test_7Hq2
← 200 mbr_7Hq2 · attorneys · grace@example.com · verified: bar admission in US-CA, US-NY, US-NJ · on

Each claim is checked in order: credential, region, coverage, availability, separation, conflict, clearance. The answer names the predicate that failed and its cure:

Call
→ POST /reviews/tsk_7Hq2/claim   # as mbr_lapsed_credential
magic values: client.email = signs@example.test, member = mbr_lapsed_credential, member = mbr_out_of_region, member = mbr_lapsed_coverage, member = mbr_eligible
← 403 ineligible · credential: attorney needs bar admission, and the verifier holds no current one for mbr_lapsed_credential.
  cure  Renew bar admission; the verifier writes it once it checks.

→ POST /reviews/tsk_7Hq2/claim   # as mbr_out_of_region
← 403 ineligible · region: attorney needs bar admission in US-CA, and mbr_out_of_region holds it in ZZ.
  cure  Add US-CA to your regions at /join; the verifier checks the admission.

→ POST /reviews/tsk_7Hq2/claim   # as mbr_lapsed_coverage
← 403 ineligible · coverage: attorney needs $1,000,000 of coverage in force per act, and mbr_lapsed_coverage's policy lapsed on 2026-10-24.
  cure  Renew the policy; the verifier writes the new term.

→ POST /reviews/tsk_7Hq2/claim   # as mbr_eligible
← 200 tsk_7Hq2 · claimed · attorney · advise · US-CA · $50.00 · 45 min · $66.67 an hour · due 2026-10-27 14:02

Because clear.check names parties, a Task goes to one member at a time in rank order, each for a window. A pass moves it on at once:

Call
→ POST /matters {"service":"contract-review","contract":"lease.pdf","client":{"name":"Ada Lovelace","email":"signs@example.test"},"client_region":"US-CA","max_usd":99,"key":"lease"}
magic values: client.email = signs@example.test, member = mbr_eligible
← job_7Hq2 · waiting · on claim · $99.00 held · advise · tsk_7Hq2

# dispatch cascade: offered to mbr_7Hq3 alone, for a window
  mbr_7Hq3      GET /tasks → tsk_7Hq2
  mbr_7Hq2      GET /tasks → nothing
  mbr_eligible  GET /tasks → nothing

→ POST /reviews/tsk_7Hq2/pass   # as mbr_7Hq3

# a pass moves it on at once: mbr_7Hq2
  mbr_7Hq3      GET /tasks → nothing
  mbr_7Hq2      GET /tasks → tsk_7Hq2
  mbr_eligible  GET /tasks → nothing

→ POST /test_clocks/default/advance {"to":"61min"}

# the window ran out: the sandbox's eligible member, last in every cascade
  mbr_7Hq3      GET /tasks → nothing
  mbr_7Hq2      GET /tasks → nothing
  mbr_eligible  GET /tasks → tsk_7Hq2

A member's conflicts ledger is theirs. A match keeps the Task from them, and no one learns which entry matched, not even the owner:

Call
→ GET /reviews   # as mbr_eligible
magic values: client.email = signs@example.test, member = mbr_eligible
← 0 Tasks: the cascade skipped a conflicted member
→ POST /reviews/tsk_7Hq2/claim   # as mbr_eligible
← 403 ineligible · conflict: A party on this review matches an entry in mbr_eligible's conflicts ledger.
  cure  None for this Task: your ledger stays yours, and no one learns which entry matched.

→ GET /me/ledger   # the owner console, with no member session
← 403 needs_a_person: Only a signer, in their own session at http://localhost:8787/attorneys, does this. A key never can.
# what the owner reads: /owner/tasks no hash of it · /owner/jobs/job_7Hq2 no hash of it · /owner/jobs/job_7Hq2/export no hash of it · /owner/events?limit=1000 no hash of it

Tiers are computed from outcomes. When no member claims a review in time, the fill ladder raises the fee, then opens it to the tier below:

Call
→ POST /test_clocks/tc_7Hq3/advance {"to":"2h"}
magic values: client.email = signs@example.test
# the record: task.posted → task.raised → task.widened → task.claimed → attested
  14:02 · task.posted · tsk_7Hq2 · $50.00 · dispatch cascade
  16:02 · task.raised · tsk_7Hq2 · $55.00 · raise 10% 2h · funded by the brand
  18:02 · task.widened · tsk_7Hq2 · opened reviewer · widen 4h
  18:02 · task.claimed · tsk_7Hq2 · mbr_7Hq4 · simulated
  ladder  raise 10% 2h: raised to $55.00 · widen 4h: opened reviewer · posted $50.00, now $55.00

Pool() covers every key, and When no one claims the ladder.