Appearance
Seller and payout screening
A marketplace decides about more than the seller it onboards: payouts can go to an account someone else holds. In this pack, rules, a model and a person decide together whether a seller can start receiving payouts. Two list rules check both names against one sanctions list the marketplace keeps and treat the hits differently (the seller's rejects, the holder's opens a review), and a model reads both names and the seller's note about the account to say whether the seller holds it itself: an account it cannot match to the seller opens a review too. Every rule result, model answer and decision is on the run's record. Pack: examples/policies/seller-payout-screening/.
A pattern for deciding about several parties, not compliance guidance: who must be screened, against which lists, and whether a hit on a third party blocks the seller are the business's to decide. The pack ships the list payout_sanctions_list with a placeholder row (Evil Corp), and its model rule calls one install, chatgpt (The chatgpt install). The model never screens a name: the list does.
The decision it automates
Whether a seller can start receiving payouts without a person looking. A seller on the list is rejected outright; a clean seller whose payouts go to a listed account holder, or to an account the model cannot match to the seller, is held for a reviewer, who decides whether the account changes or the seller goes. Only the list rejects: the model's doubt sends a seller to a person, never away.
Data expected
- A subject of kind
sellerwithdata.nameanddata.country, anddata.payoutAccountHolder: the name on the account payouts go to, which the integrating system sets to the seller's own name when the seller holds the account. data.payoutNote(optional): what the seller wrote about the payout account when it gave it, such as why payouts go to a parent company. The model reads it with the two names.- No collection flow: the marketplace already holds both names and the note.
- The
payout_sanctions_listlist (kindstring), which the pack creates with its placeholder row. - An install named
chatgptof the platform'schatgptapp, with your OpenAI API key.
Steps and rules
| Step | Type | Why |
|---|---|---|
rules | evaluate_rules | The three rules below, under the default policy: a failed block rule rejects; a failed warn rule, or a rule in error, sends the run to review. |
route | branch | rules.outcome == manual_review opens the review case; an approval or a rejection goes straight to the decision. |
payout_review | create_case | payout_review, priority high, 24-hour SLA. |
decide | emit_decision | The reviewer's decision when there was a case, otherwise from the rules: a seller hit rejects whatever the holder's check and the model said. |
| Rule | Type | Severity, weight | Fails when |
|---|---|---|---|
payout_seller_sanctions_hit | list_lookup | block, 100 | subject.name is on payout_sanctions_list |
payout_holder_sanctions_hit | list_lookup | warn, 50 | subject.payoutAccountHolder is on payout_sanctions_list, or missing |
payout_holder_is_seller | app, chatgpt's decide | warn, 30 | the model is less than 80% sure the seller holds the account |
A string list matches the trimmed value regardless of case (Rules: lists), so the list rules find a name exactly as listed; fuzzy matching, aliases and transliterations are what a screening vendor adds.
The model step
The rules step asks the model itself: payout_holder_is_seller is an app rule, which calls chatgpt's decide while the rules are evaluated (Rules: app). Its state is a sentence the rule writes (stateTemplate) from three variables its input mapping fills: the seller's name (subject.name), the account holder's name (subject.payoutAccountHolder) and the seller's note (subject.payoutNote). Each has a literal default, such as (no note), so a seller without a note is still read. It asks two questions:
| Question | Answer | Asks | Read by |
|---|---|---|---|
same_party | yes or no, threshold 0.8 | Is the account held by the seller itself? A business entity suffix (Inc., LLC, Corp.), punctuation, letter case, an abbreviation or a trade name (DBA) the note gives make no difference; a parent company, an affiliate, a partner or a person is someone else. | The rule: under 0.8 it fails, and the seller goes to review |
explained | yes or no | Does the note say who holds the account and why the payouts go there? A missing or vague note is a no. | The reviewer, in the rule's details |
The rule fails when answers.same_party.value is false (failWhen: false), and its severity is warn: the model can send a seller to a person, never reject one, and never let a listed holder through, since a holder on the list fails its own rule whatever the model says. The model answers whether two names the marketplace already has are one party; it is never asked about sanctions, politically exposed persons or company registries, which stay with the list or a screening vendor.
Because a rule asks it, a model call that fails does not fail the run: the sample key, an outage that outlasts the retries, an exhausted quota or a refusal ends the rule in error, which sends the seller to review as a failed warn rule does, with the vendor's reason in the rule's result. A dry run or a backtest has no app to call, so the rule comes back skipped there (Testing a rule before it decides anything).
What the vendor sees
Through the install, OpenAI receives the rule's sentence and its two questions: the seller's name, the account holder's name and the seller's note. Nothing else reaches it: not the country, the list or the other rules' results; no document or image reaches a model (Data protection). The model rule runs with the list rules, so the model reads every seller's names, a listed seller's included. When the holder is a person, their name is personal data that reaches OpenAI; the one-click install calls OpenAI's Responses API with storing off.
The note is the seller's own text, and a seller may write instructions into it: at worst, an answer the model was talked into gets the seller past this one rule, never past the list (Prompt injection).
The chatgpt install
A pack never carries an install: its rule calls one by name, so the pack declares it, "installs": { "chatgpt": "chatgpt" }, the platform's ChatGPT (OpenAI API). The import reports chatgpt missing (--publish publishes all the same), and the admin console's import result installs it in one click, with a preset configuration (an OpenAI connection through the Responses API, the console's first suggested model, OpenAI's hosts approved) and a sample key in place of yours. Store your OpenAI API key on the install's page (Configuration › Keys › openaiApiKey) before the workflow runs. Through the API, create the install as Installing a model app shows, named chatgpt.
Until you do, the model rule cannot answer: OpenAI refuses the sample key, the rule ends in error, and every seller the list does not reject goes to review. A model call that fails once the key is in does the same, and so does a workspace without any install named chatgpt. The run itself does not fail, because a rule asks the model rather than a step; the vendor's answer shows in the rule's result, in the invocation record and under the run's app calls. To ask Claude or Gemini instead, install that app under a name of your own and name it in the rule's app and in the pack's installs: every model app answers decide in the same shape.
What a reviewer sees
A payout_review case with the three rule results: which list rule failed, with the listed name in its reason, and the model's answers to both questions in the model rule's details, same_party with its probability. The model's call is under Vendor checks. A case queue in the admin console can pin the type (see Admin console: queues and filters).
How to adapt it
Load the sanctions lists you screen against into payout_sanctions_list (the CSV import, POST /lists/:key/import) and keep it current. Or screen through a vendor: install the platform's http-api for it, add a call_app step before rules that sends subject.name and subject.country with outputKey: "sellerScreening", and make payout_seller_sanctions_hit a comparison on the vendor's verdict, such as sellerScreening.body.hit (Adding a vendor step). Screen the holder, the seller's directors or its beneficial owners the same way: that is a list's or a vendor's job, never a model's. Write the model's questions and its 0.8 threshold for your own payout policy, or give the rule a fallback to another vendor's install, which answers when chatgpt's circuit is open or its outage outlasts the retries (Falling back to another install).
The two ways of calling an app trade off. A step records its output in the run context, where every later rule reads it and where a backtest finds it again in the recorded evaluation, and a step whose call fails fails the run. An app rule calls while the rules are evaluated: it needs no step and no output key, and a failed call sends the run to review, but a dry run or a backtest cannot call the vendor, so the rule comes back skipped there. Ask the model from a decide step before rules, with a comparison rule on its answer, when the rules must replay it.
Run it
bash
pnpm policy:import examples/policies/seller-payout-screening --publishThe check runs two fixtures against the model stub (scripts/ai-stub.mjs), whose answers a cue word in the seller's note picks. expect.json onboards Northwind Traders with payouts to Evil Corp and a vague note: the seller is not on the list and the holder is, so the holder's rule fails; cued low, the stub puts same_party at 0.05, so the model rule fails too (a score of 80); a payout_review case opens, the check rejects it and the run completes with a manual reject. expect-own-account.json onboards Bluebird Home Goods with payouts to Bluebird Home Goods LLC: neither name is listed, the stub, cued high, puts same_party at 0.95, and the run completes with an automated approve, no case opened.