Verify a member

The running jobjob_7Hq2 · waiting · on approval · $49.00 held

An application, whole, is sent to the Pool's verifier as member.applied. The verifier checks it against the source that issued the credential, and writes back only what it checked, on its own write path with its vt_ token. Each write replaces the last, and freshness runs from it.

How does what a member claims become what the Pool relies on? A verifier you run checks each application against the source that issued the credential, and writes back only what it checked. Nobody else can write it, and nothing a member says counts until it does.

A member applies at the Pool's /join, one step at a time:

Delivery portal/attorneys/joinnothing waiting
Delivery portal/attorneys/joinnothing waiting

Once every step is answered, the Pool emits member.applied with every answer, for the verifier:

Call
→ GET /owner/events?type=signer.applied   # the owner's feed: what the verifier is sent
200 · list · 1 signer.applied · magic values: none
{
  "type": "OK",
  "object": "list",
  "data": [
    {
      "id": "evt_5gX1X2B5swzY61",
      "object": "event",
      "seq": 1,
      "type": "signer.applied",
      "created": "2026-10-25T14:02:11.000Z",
      "job": null,
      "service": null,
      "data": {
        "member": "mbr_7Hq2",
        "pool": "attorneys",
        "email": "grace@hopper.example",
        "answers": {
          "name": "Grace Hopper",
          "credential": "patent agent",
          "number": "71,234",
          "regions": [
            "US-CA"
          ],
          "areas": [
            "patents"
          ],
          "hours": 10,
          "turnaround": "24h",
          "min_fee": 120
        },

The verifier writes through POST /<pool>/members/:id/verify, with its own vt_ token as a bearer. facts.credentials names each credential held and the regions it's held in; facts.coverage is optional, the policy in force per act and when it ends. A secret key is refused there. Here is a 20-line verifier that looks the number up on a roster, and writes only what the roster says:

Run
cat verify.ts
magic values: none
// The attorneys Pool's verifier, illustrative: services.dev sends it member.applied when an application is whole. It
// looks the number up on the roster that issued it, and writes back only what the roster says, through the Pool's
// verifier door with its vt_ token. Each write replaces the last; nothing merges, and freshness runs from it.
type Applied = { member: string; pool: string; answers: { name?: string; credential?: string; number?: string; regions?: string[] } }
type Entry = { name: string; credential: string; regions: string[]; standing: 'good' | 'suspended'; coverage?: { per_act: number; until: string } }

export async function verify(event: Applied, roster: Record<string, Entry>, door: { url: string; token: string; fetch?: typeof fetch }) {
  const a = event.answers
  const entry = a.number ? roster[a.number] : undefined
  const holds = entry?.standing === 'good' && entry.name === a.name && entry.credential === a.credential
  // Only the regions the roster and the application both name, or everywhere for a credential that counts everywhere.
  const regions = !holds ? [] : entry.regions.includes('*') ? ['*'] : entry.regions.filter(r => (a.regions ?? entry.regions).includes(r))
  const facts = { credentials: holds ? { [entry.credential]: regions } : {}, ...(holds && entry.coverage ? { coverage: entry.coverage } : {}) }
  const res = await (door.fetch ?? fetch)(`${door.url}/${event.pool}/members/${event.member}/verify`, {
    method: 'POST',
    headers: { authorization: `Bearer ${door.token}`, 'content-type': 'application/json' },
    body: JSON.stringify({ facts }),
  })
  return { status: res.status, facts, why: holds ? 'on the roster, in good standing' : entry ? `the roster's entry for ${a.number} doesn't match, or isn't in good standing` : `no ${a.number ?? 'number'} on the roster` }
}
Call
→ await verify(event.data, roster, { url, token: 'vt_test_7Hq2' })   # the member.applied event above
magic values: none
← {"status":200,"why":"on the roster, in good standing"}
  facts {"credentials":{"patent agent":["*"]},"coverage":{"per_act":1000000,"until":"2027-06-30T00:00:00Z"}}

A number the roster holds in another name, or not in good standing, writes no credential, and the member is offered nothing:

Call
→ await verify(event.data, roster, { url, token })   # mbr_7Hq3, NY-777
magic values: none
← {"status":200,"why":"the roster's entry for NY-777 doesn't match, or isn't in good standing"}
  facts {"credentials":{}}
Call
→ POST /attorneys/signers/mbr_7Hq2/verify   Authorization: Bearer sk_test_7Hq2
magic values: none
← 401 key_invalid: This door takes the network's verifier token, Authorization: Bearer vt_…, and nothing else.

Every write joins the member's journal, and the latest is what is true: nothing merges, so a write that leaves out coverage means none is in force. The member's record shows it, and who wrote it:

Call
→ POST /attorneys/join {"email":"grace@hopper.example"}   # the member reads their own record
magic values: none
← 200 mbr_7Hq2 · attorneys · grace@hopper.example · verified: patent agent in * · on
  verified {"credentials":{"patent agent":["*"]},"coverage":{"per_act":1000000,"until":"2027-06-30T00:00:00Z"},"at":"2026-10-25T14:02:11.000Z","by":"vt_test_7Hq2","fresh_until":"2026-11-24T14:02:11.000Z","fresh":true}

The member's own portal shows the same card, their name as admitted, the credential and number they gave, how long it stays fresh, and the coverage written:

Delivery portal/attorneysnothing waiting

fresh_until is the write's time plus the Pool's fresh, 30 days unless you set it. It is read at each use, so time alone makes a fact stale: a stale member is offered nothing until the verifier writes again, and nothing waits on them. An unverified member is never offered a Task. The member verified at join, on a clock 32 days on:

Call
→ POST /test_clocks {"frozen_at":"2026-11-26T14:02:11Z"}
magic values: client.email = signs@example.test
← tc_7Hq3
→ POST /matters {"service":"contract-review","contract":"lease.pdf","client":{"name":"Ada Lovelace","email":"signs@example.test"},"client_region":"US-CA","max_usd":99,"test_clock":"tc_7Hq3","key":"stale"}
← job_7Hq2 · waiting · on claim · $99.00 held · advise · tsk_7Hq2

→ POST /reviews/tsk_7Hq2/claim   # as mbr_7Hq2
← 403 ineligible · availability: mbr_7Hq2's verified facts went stale on 2026-11-24.
  cure  The verifier rechecks your facts; nothing waits on you.

dev stands in a fixture verifier: it writes what the application claims, labeled simulated, the moment an application is whole. Your own verifier's writes replace it, as above. Once deployed, each Pool's vt_ token is minted at deploy, and member.applied reaches your verify URL, signed and retried.

KeyWrittenLeft outDoes
verifyUrl | FnrequiredThe verifier: the only writer of verified facts, on its write path with its vt_ token. On localhost a fixture verifier writes what the application claims, labeled simulated.
freshWithin'30d'Re-verify every: freshness is read at use, so time alone makes a fact stale.