> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pesapay.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Profiles and policies

> A site is described by a profile, a rule by a policy. Adding a rail adds two files and no code.

The platform is one capture-and-proof engine driven by data. Nothing about a bank, a gig platform or a payment lives in the engine.

| Layer | Describes | Must not contain |
| - | - | - |
| **Profile** | An HTTPS endpoint: login URL, cookie domains, the request to build, the reply's fields and record bounds | A threshold, a comparison, an expected value |
| **Policy** | What is proved about a profile's fields: predicates, outputs, context, nullifier, expiry | A URL, a header or a field offset |
| **Consumer** | Anything that verifies a claim: a backend, a contract | Facts re-derived from receipts |

## Profile

```rust theme={null}
pub struct Profile {
    pub profile_id: String,               // for example "wise-transfer-status.v1"
    pub version: u32,
    pub login: LoginSpec,                 // url, cookie_domains
    pub requests: Vec<RequestTemplate>,   // method, url_template, headers, body_template
    pub reply: ReplySchema,               // fields, max_total_len
    pub records: RecordLayout,            // max_records, max_record_len
}

pub struct FieldSpec {
    pub name: String,
    pub kind: FieldKind,                  // String | Integer | Decimal { scale } | Bool | Timestamp
    pub locator: FieldLocator,            // Fixed { offset } | JsonPath { path } | Regex { pattern }
    pub max_len: u32,
    pub request_index: u32,
}
```

A field is bound by its JSON `"key":`, so every key a policy reads must appear once in the reply. That is why a payment profile reads one transfer's detail endpoint, never a list.

## Policy

```rust theme={null}
pub struct Policy {
    pub policy_id: String,
    pub version: u32,
    pub profile_id: String,
    pub predicates: Vec<Predicate>,
    pub outputs: Vec<OutputSpec>,
    pub context_digest_rule: ContextDigestRule,
    pub nullifier: Option<NullifierSpec>,   // scope + fields
    pub expiry_secs: u64,
    pub holder_binding: bool,
}

pub enum Predicate {
    Equals  { field: String, value: Value },
    Range   { field: String, min: Value, max: Value },
    Sum     { fields: Vec<String>, min: Value, max: Value },
    Average { fields: Vec<String>, min: Value, max: Value },
    Bucket  { field: String, edges: Vec<Value> },
    Prefix  { field: String, prefix: String },
}
```

Values are typed: `String`, `Integer`, `Decimal { units, scale }`, `Bool` and `Timestamp`. Each has one canonical byte form, so a policy hashes the same way everywhere.

## Context predicates

A payment proof must match one order, not a constant. Context predicates compare a field with a value from the session's context instead of the policy:

* `Equals(field, ctx.key)`: the field equals a context value, such as the order's exact amount.
* `HashEquals(field, ctx.key)`: `SHA-256(canonical(field))` equals a context value. Used for payee and payer identities, so the identity never leaves the phone.

The verifier derives these public inputs from the context it was given by the relying party, never from the phone.

## Bundles

Profiles and policies ship in a signed **policy bundle**. The SDK pins the bundle by digest and reads the login URL, cookie domains and request from it. A profile and policy pair compiles to one circuit ID, and the claim names the circuit ID and config hash it was proved under.

## A second rail is a second pair of files

Adding a payment rail must not change the enclaves, the circuit compiler, the verifier, the SDK or any contract. If it would, the schema is wrong and the specification changes, not the core.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.