# Moderation

> The policy is prose your trust-and-safety team owns. The thresholds are a diffable object. Hard rules stay in code.

Section: Scenarios · HTML: https://jevascript.org/docs/scenarios/moderation · Markdown: https://jevascript.org/docs/scenarios/moderation.md

**Source:** [`examples/launch/03-moderation.ts`](https://github.com/hakantapanyigit/jevascript/blob/main/examples/launch/03-moderation.ts)

## The problem

Listings on a marketplace need a decision before they go live: publish, hold for verification, send to a moderator, or reject. Some of that is judgement (is this a counterfeit? is the seller steering off-platform?). Some of it is policy that has nothing to do with the text (anything over €10,000 needs identity verification).

## The schema

```ts
// moderate.ts
import { boolean, defineSchema, enumOf, object, score } from "jevascript"

export const moderate = defineSchema({
  name: "listing-moderation",
  version: "7",

  instructions: `
    Review a listing submitted to Northwind Market, a marketplace for used
    professional equipment. Judge only the listing text and declared fields.
    A listing is not prohibited merely because the item is expensive or unusual.
  `,

  context: {
    prohibited: `
      Weapons and weapon parts. Prescription drugs and medical devices requiring a
      licence. Counterfeit or replica branded goods. Recalled equipment. Anything
      whose sale requires a licence the seller does not claim to hold.
    `,
    restricted: `
      Items over EUR 10,000 require identity verification before listing.
      Industrial machinery requires a declared safety certificate.
    `,
  },

  output: object({
    prohibited: boolean({
      describe: "This listing is for a prohibited item.",
      falseWhen: "An item that is merely expensive, niche, or requires care to ship.",
    }),
    counterfeitRisk: score(0, 100, "This listing is likely to be a counterfeit or replica."),
    offPlatform: boolean({
      describe: "The seller is steering the buyer to pay or communicate outside the platform.",
      trueWhen: "Shares a phone number, personal email, or asks for a bank transfer or crypto.",
    }),
    misleading: score(0, 100, "The description overstates the condition or provenance of the item."),
    missingInfo: boolean({
      describe: "A buyer could not reasonably decide from this description alone.",
    }),
    action: enumOf(
      {
        publish: "Nothing here needs a human",
        verify: "Publish only once the seller completes identity or certificate checks",
        review: "A moderator should look at this before it goes live",
        reject: "This cannot be listed",
      },
      "What should happen to this listing",
    ),
  }),
})
```

## The decision

The schema includes an `action` field: the model's own suggestion. It is one input, not the decision. Hard policy wins, and the price rule is not the model's job to remember.

```ts
// decide.ts
const m = await moderate({ price, category, text })

const decision = m.prohibited || m.offPlatform
  ? "reject"
  : m.counterfeitRisk > 60
    ? "review"
    : listing.price > 10_000
      ? "verify"
      : m.misleading > 60 || m.missingInfo
        ? "review"
        : "publish"
```

`listing.price > 10_000` is deterministic. The policy text in `context` mentions the same threshold so the model understands the domain, but the code is what enforces it.

## Why the thresholds are here

`counterfeitRisk > 60` is a number the trust-and-safety team will want to move. Because it is in code, moving it is a one-line diff with a reviewer, a test, and a deploy timestamp. Because the probability behind it is logged, the effect of the move can be measured on last month's listings before it ships.

## What to test

- A watch described as "indistinguishable from the real thing" with a WhatsApp number → `reject`, on `offPlatform` alone if the counterfeit score is stubbed low.
- A €14,500 mill with a clean description → `verify`, from the price rule, with the model stubbed to `publish`.
- A boxed camera with a receipt → `publish`.
