Appearance
Sanctions and PEP escalation
Two escalation paths from the screening signals a subject carries, where rules, a model and reviewers decide together and every step is on the record: a sanctions hit opens a critical case before any rule runs or any model is asked, while softer signals (a politically exposed person, adverse media) are scored, and a review they lead to opens with a brief a model writes from the subject's own account of their funds. Pack: examples/policies/sanctions-and-pep-escalation/.
A pattern for expressing an escalation policy, not compliance guidance: what counts as a hit, which lists are screened and what a reviewer must do with a match are the tenant's and the vendor's business. The signals are fields of the subject record that the integrating system sets. A model, through the chatgpt install, writes the reviewer a brief from what the subject wrote: it is never asked on the sanctions path, never reads the screening, and never says whether anyone is sanctioned or politically exposed (The model step).
The decision it automates
Whether a subject can proceed without a person looking, must be looked at within hours, or within a day. The point of the pack is the ordering: the expensive, time-critical path is a branch at the start of the run, not a rule in a set, and no model sits between a sanctions hit and its case.
Data expected
- A subject with
data.sanctionsHit, the outcome of the sanctions screen the integrating system ran, and the flagsdata.pepanddata.adverseMediathat it sets from its own sources or from the same screening. A flag left out fails its rule, which sends the subject to review. data.sourceOfFunds, optional: the subject's own account of where the money they will use comes from, as onboarding collected it. Without it, a review opens without a brief.- No collection flow: screening happens on data the platform already holds.
Steps and rules
| Step | Type | Why |
|---|---|---|
hit | branch | subject.sanctionsHit == true goes to escalate; everything else to rules. |
escalate | create_case | sanctions_escalation, priority critical, 4-hour SLA. No rules run and no model is asked: the reviewer decides on the subject and the screening behind the flag. |
rules | evaluate_rules | The screening_pep rule set, for subjects without a sanctions hit. |
route | branch | rules.outcome == manual_review goes on to the softer case; the rest is decided by the rules. |
has_account | branch | A subject with sourceOfFunds goes to brief; one without goes straight to the case. |
brief | call_app | chatgpt's generate: a brief for the reviewer from the subject's account of their funds, under brief (The model step). |
pep_review | create_case | pep_review, priority high, 24-hour SLA. |
decide | emit_decision | The reviewer's decision when there was a case (either one), otherwise from the rules. A run that took the escalation branch has no rule results; the decision is the reviewer's alone. |
The rule set screening_pep sums weights into two bands, 0-40 approve and 40-100 manual_review: either signal alone lands in review, and there is no automatic reject on these signals, by design. No model takes part in it.
| Rule | Type | Severity, weight | Fails when |
|---|---|---|---|
screening_pep_declared | comparison | warn, 60 | subject.pep == true (passWhen: false turns the match into a failure) |
screening_adverse_media | comparison | warn, 40 | subject.adverseMedia == true |
The model step
brief asks the model for a brief the reviewer reads before deciding. Its prompt has one variable, account, mapped from subject.sourceOfFunds. The step's literal account ("The customer gave no account of their funds.") stands in should the field be missing, so the prompt always has its variable; has_account sends a subject without one straight to the case. The answer is an object, ReviewerBrief, under brief.output:
| Field | Holds |
|---|---|
summary | The account in one or two sentences, in the subject's own terms |
sources | Each source of money the account names, in a few words |
checkable | Whether the account names at least one source a document could confirm, such as an employer and a role, a business and its sale, an inheritance or the sale of a property |
questions | What the reviewer may ask the subject, from what the account leaves open |
The step's instructions keep the model to the account: it says only what the account says, and never whether the subject is sanctioned or politically exposed, whether anything is true, lawful or suspicious, or what the reviewer should decide. The brief is context for the reviewer: nothing routes on it, and the reviewer decides.
What the vendor sees
brief sends OpenAI, through the chatgpt install, the step's instructions, its prompt with the subject's account of their funds and the brief's schema, and nothing else of the run: not the subject's name or country, the sanctions hit, the PEP and adverse-media flags or any screening result. The account may name employers, family members and amounts. It is text the model reads: an instruction written into it can only change the brief, which the reviewer reads beside the account itself on the subject record (Prompt injection). OpenAI's own terms say what it keeps; the console's preset calls its Responses API with storing off (Data protection).
The chatgpt install
The pack declares the install it calls, "installs": { "chatgpt": "chatgpt" }; installs are never part of a pack. The admin console's import result installs chatgpt in one click: ChatGPT (OpenAI API) on OpenAI's Responses API, with a sample key in place of yours. Replace the key with your OpenAI API key on the install's page (Connect › Apps › ChatGPT (OpenAI API) › chatgpt, under Keys) or through POST /app-installs/chatgpt/secrets/openaiApiKey.
Until then, a run that reaches brief (a subject the rules send to review, with an account of their funds) fails there and no case opens; so does a run whose model call fails for another reason, such as an outage that outlasts the retries, an exhausted quota or a refusal. The vendor's answer shows in the invocation record and under the run's app calls. Once the key is in, Replay from step on the run's page runs it again from brief (Admin console: Runs). A sanctions hit escalates whatever the key, and the runs that never reach brief (subjects the rules clear, and reviews without an account) go on as the rules decide.
pnpm policy:import reports the install as missing in a workspace without one, where a run that reaches brief fails with unknown_install: create it as Installing a model app shows, named chatgpt. Every model app that answers prompts takes the same input and answers in the same shape, so brief can call an install of claude or gemini instead, declared in installs with its app, or keep chatgpt with a fallback on a second install that answers while OpenAI is down (Falling back to another install).
What a reviewer sees
A sanctions_escalation case at the top of the queue (critical, four hours) with the subject record; or a pep_review case with the two rule results and, when the subject gave an account of their funds, the model's brief under brief in the run context: the account in a sentence or two, the sources it names, whether one of them is a source a document could confirm, and the questions it leaves open. A case queue in the admin console can pin either type (see Admin console: queues and filters).
How to adapt it
Screen through a vendor instead of trusting the subject's flag: install the platform's http-api for it, add a call_app step before the hit branch that sends subject.name and subject.country with outputKey: "sanctions", and point the branch at the vendor's verdict, such as sanctions.body.hit (Adding a vendor step); the case context then carries the vendor's matches and scores, and the pack declares that install beside chatgpt, "installs": { "chatgpt": "chatgpt", "screening": "http-api" }. An app rule through the same install can replace screening_pep_declared, with literal input whose request asks for politically exposed persons and a resultPath on the vendor's answer, such as body.hit, so the PEP signal comes from the vendor instead of the subject record (Rules). Add a score_threshold on the best match score to route weak matches to the softer review instead of the critical case. Map a source-of-wealth answer from onboarding into the brief's prompt as a second variable when your reviews ask for one.
Run it
bash
pnpm policy:import examples/policies/sanctions-and-pep-escalation --publishpnpm policies:check runs three fixtures. The model stub (scripts/ai-stub.mjs) answers for OpenAI, following the cue word in the subject's account ([[stub:low]]); the pack carries none.
expect.json: a politically exposed person whose account of their funds is savings from a salary and some investments. The PEP rule fails (a score of 60, a review), the model writes the brief (checkablefalse: no source a document could confirm), and apep_reviewcase opens. The check approves it and the run completes with a manualapprove.expect-sanctions-hit.json: a company whose record carriessanctionsHit: true. The run skips the rules and the model and opens asanctions_escalationcase; the check rejects it and the run completes with a manualreject.expect-cleared.json: a subject with no flags. The rules approve (a score of 0) and no model is asked: an automatedapprove.