---
title: "Policy API"
description: "OpenPit exposes custom policy hooks for two stages:"
---

<!-- markdownlint-disable MD013 MD033 -->
# Policy API

OpenPit exposes custom policy hooks for two stages:

- `Start stage`: cheap checks that must run for every request.
- `Main stage`: deeper checks that can emit one or more rejects and register
  reversible mutations.

Behavioral contract first, then language-specific examples.

## Stage Contracts

- Start stage returns one reject outcome or pass-through.
- Main stage can collect multiple rejects and register reversible mutations.
- Main-stage context provides read-only access to request data.
- Main-stage mutations are committed only when the full `execute request`
  step succeeds.
- A registered mutation's commit and rollback callbacks are finalizers and have
  no right to fail; a failure arms an engine kill switch whose reach follows the
  registering policy. See
  [Account Blocking - Mutation Finalizer Contract](Account-Blocking.md#mutation-finalizer-contract).

[Account retirement](Account-Retirement.md) has a separate policy hook: check for
zero, unused account state and register its removal for commit. The default
hook declares that the policy holds no account-scoped state.

For account-adjustment batch policy hooks, see
[Account Adjustments](Account-Adjustments.md).

### Context Lifetime

Every stage hook receives a context as its first argument. How long that
context stays usable is language-specific:

- Go: contexts, collectors, `tx.Mutations`, and engine-view model arguments are
  callback-scoped - valid only during the hook call; using one after the hook
  returns is undefined behavior. An `AccountControl` stays valid through its
  transaction's commit or rollback. See
  [Custom Go Types](Custom-Go-Types.md#lifecycle--payload-contract) for the full
  contract and copy rules.
- Python: the context owns its state, so holding a reference is safe; the
  `AccountControl` it exposes still records a block only within its own
  pre-trade transaction, from the callback through that request's commit or
  rollback. A block recorded after that transaction completes is unspecified.
- JavaScript: the context owns its state and may be retained; once the owning
  operation is finalized, its `AccountControl` and drop-copy mutation recorder
  throw `LifecycleError` rather than writing into a finished transaction.
- C++: the context is non-owning and callback-scoped, and is neither copyable
  nor movable - valid only for that call. See
  [Custom Cpp Types](Custom-Cpp-Types.md).
- Rust: the hook borrows the context, so the compiler already bounds its use
  to the call.

For drop copy, account-control operations requested through these contexts are
applied in order before the accepted operation is returned. They are not
retained as caller rollback callbacks and are not undone by a later operation
rollback.

## Drop-Copy Policy Hooks

Drop copy runs the same start and main policy hooks in registration order. The
context flag tells a policy that ordinary risk or compliance rejects are
non-enforcing because the order already happened. A policy should branch on the
flag only when necessary to preserve bookkeeping - for example, Spot Funds
forces TrackOnly. Pure admission policies such as order validation and order
size limits do no work in drop-copy mode.

The context surfaces use each language's native naming:

| Language | Detect drop copy | Register a start-stage mutation |
| --- | --- | --- |
| Go | `ctx.IsDropCopy()` | `ctx.RecordDropCopyStartMutation(commit, rollback)` |
| Python | `ctx.is_drop_copy` | `ctx.record_drop_copy_start_mutation(mutation)` |
| JavaScript | `ctx.isDropCopy` | `ctx.recordDropCopyStartMutation(mutation)` |
| C++ | `ctx.IsDropCopy()` | `ctx.RecordDropCopyStartMutation(commit, rollback)` |
| Rust | `ctx.is_drop_copy()` | `ctx.record_drop_copy_start_mutation(mutation)` |

Start-stage hooks have no ordinary mutation return channel, so stateful custom
policies use this recorder only during an active drop-copy callback. Apply
tentative state before registering a start-stage or main-stage mutation. Both
recorders take the same infallible commit and rollback pair as an ordinary
main-stage mutation: the commit callback runs when the caller commits the
returned operation, and the rollback callback runs when the caller rolls it
back or releases it unfinalized. If evaluation fails, the engine runs every
collected rollback in reverse registration order before returning the rejects,
and no operation is produced. Main-stage policies keep using their ordinary
mutation channel.

There is no public `record drop-copy account block` hook. A custom policy asks
for a block in the normal way by returning an account-scoped reject. Drop copy
does not use that reject to deny the historical order. Ordered account-control
operations are applied before the accepted operation is returned and remain in
effect even if the caller later rolls the operation back. To abort the
bookkeeping because the policy cannot calculate or apply the historical effect,
return a standardized code for which
the binding's evaluation-failure classifier is true; see
[Reject Codes](Reject-Codes.md#evaluation-failures-and-drop-copy).

## Concurrency and Policy State

Custom policy state must not be read or mutated in parallel with engine
calls on the same engine instance.

- Unsafe pattern: one thread executes `start stage` or `execute request`
  while another thread reads or mutates fields used by the same policy
  callbacks.
- If shared access is unavoidable, synchronization is fully owned by the
  host application (locks, serialized access, actor loop, etc.).
- Preferred pattern: keep policy state mutations inside engine calls and
  feed external corrections through `apply account adjustments`.

### Calling Back Into the Engine From a Callback

A callback must not re-enter the engine that invoked it. The call is already
inside that engine's storage access, so re-entering it aliases state the sync
mode cannot protect.

In JavaScript the rule is enforced across engine methods, retained `accounts`
and `configure` facades, requests, reservations, and drop-copy operations. They
throw `LifecycleError` when the same engine is re-entered synchronously from one
of its own policy or mutation callbacks. The guard also covers the implicit
rollback that runs when a `Reservation` or `DropCopyOperation` is released
without `commit` or `rollback`, so a mutation `rollback` closure cannot reach
back into its own engine during release. Driving a *different* engine instance
from a callback remains allowed. Match on the error class rather than its
message.

In Python and C++, a nested engine or market-data call made from inside a
callback does not disturb the exception owned by the operation that encloses
it. Each operation owns its own callback-exception slot. A policy may catch a
nested lookup failure without losing the enclosing operation's failure, and a
failing account-group lookup is reported by the call that asked for it rather
than being read as "this account has no group". Python keeps the first callback
exception raised within one operation; remaining callbacks still run, but a
later exception does not replace the first.

### When a Callback Itself Fails

A callback that raises or throws is an evaluation failure, not a verdict, and
drop copy handles it exactly like an ordinary pre-trade call: the engine
compensates the mutations it collected and reports the failure through the
binding's own channel. Python rethrows the original callback exception. C++
rethrows the original exception too, once cleanup has finished. JavaScript
throws `PolicyCallbackError` with the original value as `cause`, whatever its
class. An operation that an engine defect abandoned reports that
`InternalError` instead of wrapping it.

A mutation finalizer is the one callback that has no right to fail. When one
fails during a caller-driven `commit` or `rollback` it still reaches the same
binding-native channel as the equivalent reservation callback, and the void call
still returns normally - but the failure is not dropped: the engine arms a kill
switch, and for a mutation registered by a custom policy that block covers every
account, not only the pipeline's. Every mutation registered through a binding is
a custom-policy mutation. Implicit rollback on release performs the same cleanup
without intentionally throwing, because no caller is left to receive an error;
the kill switch is then the only channel. The full contract, including the cause
the engine records and how a policy author should avoid it, is in
[Account Blocking - Mutation Finalizer Contract](Account-Blocking.md#mutation-finalizer-contract).

Go does not let a panic cross the cgo boundary. A panic from either pre-trade
hook - including a drop-copy hook - from either dry-run hook, or from the
account-adjustment hook becomes an order-level `SystemUnavailable` reject whose
details contain the recovered value. A panic
from `ApplyExecutionReport` becomes a `SystemUnavailable` account block
that the engine records for that account. A panic from policy teardown has no
result channel left, so it is contained and reported to the process-wide handler
installed with `openpit.SetTeardownFailureHandler`; without one it leaves no
trace.

A native Rust callback that panics unwinds to the caller unchanged; the SDK
adds no containment around policy or mutation callbacks in any pipeline,
including drop copy. Rust policy and mutation callbacks must therefore not
panic. The foreign bindings above convert their own callback failures before
this boundary, so their documented channels are unaffected.

A Rust panic at the JavaScript WebAssembly boundary surfaces as
`InternalError`, not a bare trap. It permanently poisons the loaded module
instance: every later call that would reach core state returns the same
`InternalError` instead of running. That covers the whole state-bearing
surface - `Engine`, `Request`, `Reservation`, `Accounts`, `Configurator`,
`AccountControl`, `Context`, `MarketDataService`, and `ReferenceBook`. The
instance never recovers: discard every handle from it and reload the module
before continuing.

## Language Interfaces

### Go Interface

The Go SDK exposes:

- unified interface `pretrade.Policy` - all stage hooks and the
  account-adjustment callback in one interface;
- for custom order/report types,
  `pretrade.ClientPreTradePolicy[Order, Report]` - the same four callbacks,
  but order and report arrive as the typed project struct (account-adjustment
  still uses `model.AccountAdjustment`);
- adapters with payload validation: `pretrade.NewSafeClientPreTradePolicy`;
- adapters without validation, for SDK-controlled paths:
  `pretrade.NewUnsafeFastClientPreTradePolicy`;
- built-in native policies are registered via the `Builtin` builder method.

Policies with account-scoped state implement the optional
`pretrade.AccountRetirementPolicy` interface and its `RetireAccount` method.

The post-trade hook returns account blocks and may push group-tagged
account-adjustment and account-PnL outcomes into `pretrade.PostTradeAdjustments`
and `pretrade.PostTradePnls`. The three channels are independent.

### Python Interface

Python exposes a unified policy class over record-style `openpit.Order` and
`openpit.ExecutionReport`:

- unified class: `openpit.pretrade.Policy` - all stage hooks and
  account-adjustment callback with default no-op implementations

Business outcomes are returned, not raised:

- start stage returns `Iterable[PolicyReject]`
- main stage returns `PolicyPreTradeResult`
- account adjustment returns `PolicyAccountAdjustmentResult`; its independent
  `rejects`, `mutations`, `account_blocks`, and `account_adjustments` channels may
  be omitted or empty for a no-op

Policies can register:

- `Mutation(commit=callable, rollback=callable)`

### JavaScript / TypeScript Interface

JavaScript exposes one structural policy interface over the submitted order and
execution-report model types:

- unified interface: `Policy<OrderModel, ExecutionReportModel>`;
- start stage: `checkPreTradeStart(ctx, order)`;
- optional read-only start dry-run:
  `checkPreTradeStartDryRun(ctx, order)`;
- main stage: `performPreTradeCheck(ctx, order)`;
- optional read-only main dry-run:
  `performPreTradeCheckDryRun(ctx, order)`;
- post-trade: `applyExecutionReport(ctx, report)`;
- account adjustment:
  `applyAccountAdjustment(ctx, accountId, adjustment)`.

Business outcomes are returned as `PolicyReject`, `PolicyPreTradeResult`,
`PolicyAccountAdjustmentResult`, `PolicyDecision`, and mutation values. A thrown callback exception is an API
failure and is re-thrown to the caller. Each policy receives a fresh clone of
the submitted order or report, including host-specific fields; account
adjustments use the concrete OpenPit `AccountAdjustment` model.

### C++ Interface

C++ has no single policy base class. A direct `CustomPolicy<Handler>` receives
its name in the constructor, and the handler exposes one or more stage hooks
detected at compile time; any other hook is registered as null and treated as
"accept by default":

- start stage: `std::optional<Reject> CheckPreTradeStart(const Order&) const` -
  return a `Reject` or `std::nullopt`
- optional read-only start dry-run:
  `std::optional<Reject> CheckPreTradeStartDryRun(const Order&) const`
- main stage: `void PerformPreTradeCheck(const Context&, tx::Mutations&, Result&, PolicyDecision&) const` -
  report zero or more rejects through `PushReject(decision, ...)`, and optionally
  register mutations or push lock prices / outcomes into the collectors
- optional read-only main dry-run:
  `void PerformPreTradeCheckDryRun(const Context&, tx::Mutations&, Result&, PolicyDecision&) const`
- post-trade: `std::vector<accounts::AccountBlock> ApplyExecutionReport(const PostTradeContext&, const ExecutionReport&, PostTradeAdjustments&, PostTradePnls&) const` -
  return the account blocks raised, optionally pushing group-tagged adjustment
  and account-PnL outcomes into the two collectors
- account adjustment: `PolicyAccountAdjustmentResult ApplyAccountAdjustment(const accountadjustment::Context&, param::AccountId, const accountadjustment::AccountAdjustment&, tx::Mutations&, AccountOutcomes&) const` -
  validate one adjustment, optionally registering mutations and pushing outcomes;
  return accepted account blocks through the result's `accountBlocks`

Those are the direct `CustomPolicy<Handler>` signatures. A typed
`PolicyAdapter` client policy additionally exposes `Name()` and prepends
`const ClientOrder&` to the main-stage and main dry-run signatures, substitutes
`const ClientOrder&` in the start and start dry-run signatures, and substitutes
`const ClientReport&` in the post-trade signature. Missing dry-run hooks fall
back to the corresponding normal hook. The adapter performs the selected
checked or unchecked cast before invoking a hook. The name passed to
`CustomPolicy` must match the client policy's `Name()` so engine registration
and adapter type-mismatch diagnostics carry one stable identity. A type
mismatch is an API wiring failure reported as `openpit::Error`; the adapter
never converts it into a `Reject`.

The handler is wrapped in an adapter that fixes the cast mode and the concrete
order/report types. `PolicyAdapter` registers its main hook plus every optional
start, dry-run, post-trade, and account-adjustment hook exposed by the same
client-policy instance; `StartPolicyAdapter` is the start-only compatibility
wrapper:

- `StartPolicyAdapterWithSafeSlowArgType<Policy, Order, Report>` and
  `PolicyAdapterWithSafeSlowArgType<Policy, Order, Report>` - recover the typed
  order/report from the context with a checked cast; a payload type mismatch
  throws `openpit::Error` with the policy name, expected client type, and actual
  type;
- the `...WithUnsafeFastArgType` variants skip the check on SDK-controlled paths.

The adapter is registered through `openpit::pretrade::CustomPolicy<Adapter>` on
the `EngineBuilder`. `CustomPolicy` contains callback exceptions inside the SDK
boundary; the invoking C++ engine method, drop copy included, rethrows the
original exception after the call returns. Exceptions represent API
failures; expected business outcomes are returned as `Reject` /
`PolicyDecision` values.

### Rust Interface

Rust exposes a unified trait for custom policies and caller-defined order
contracts:

- unified trait:
  `PreTradePolicy<Order, ExecutionReport, AccountAdjustment, Sync>`, where
  `Sync: SyncMode + ?Sized`
  - all stage hooks and account-adjustment callback with default no-op
  implementations (only `name` is required)
- start-stage callback receives: `&PreTradeContext`, `&Order`
- main-stage callback receives: `&PreTradeContext`, `&Order`, `&mut Mutations`
- account-adjustment callback receives: `&AccountAdjustmentContext`,
  `AccountId`, `&A`, `&mut Mutations`

Implement `PreTradePolicy::retire_account` if the policy keeps account-scoped
state; see [Account Retirement](Account-Retirement.md).

### Example: Custom Main-Stage Policy

<details>
<summary>Go</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/go/examples_wiki_test.go -->
```go
package main

import (
 "fmt"

 "go.openpit.dev/openpit/accountadjustment"
 "go.openpit.dev/openpit/model"
 "go.openpit.dev/openpit/param"
 "go.openpit.dev/openpit/pretrade"
 "go.openpit.dev/openpit/reject"
 "go.openpit.dev/openpit/tx"
)

type NotionalCapPolicy struct {
 // Policy-local config: reject any order above this absolute notional.
 MaxAbsNotional param.Volume
}

func (p *NotionalCapPolicy) Close() {}

func (p *NotionalCapPolicy) Name() string { return "NotionalCapPolicy" }

func (p *NotionalCapPolicy) PolicyGroupID() model.PolicyGroupID {
    return model.DefaultPolicyGroupID
}

func (p *NotionalCapPolicy) CheckPreTradeStart(
 pretrade.Context,
 model.Order,
) []reject.Reject {
 return nil
}

func (p *NotionalCapPolicy) PerformPreTradeCheck(
 _ pretrade.Context,
 order model.Order,
 _ tx.Mutations,
 _ pretrade.Result,
) []reject.Reject {
 operation, ok := order.Operation().Get()
 if !ok {
  return reject.NewSingleItemList(
   reject.CodeMissingRequiredField,
   p.Name(),
   "required order field missing",
   "operation is not set",
   reject.ScopeOrder,
  )
 }

 // Translate the public order surface into one number that this policy
 // can reason about: requested notional.
 tradeAmount, ok := operation.TradeAmount().Get()
 if !ok {
  return reject.NewSingleItemList(
   reject.CodeMissingRequiredField,
   p.Name(),
   "required order field missing",
   "trade_amount is not set",
   reject.ScopeOrder,
  )
 }

 var requestedNotional param.Volume
 if tradeAmount.IsVolume() {
  requestedNotional = tradeAmount.MustVolume()
 } else {
  price, ok := operation.Price().Get()
  if !ok {
   return reject.NewSingleItemList(
    reject.CodeOrderValueCalculationFailed,
    p.Name(),
    "order value calculation failed",
    "price not provided for evaluating notional",
    reject.ScopeOrder,
   )
  }
  notional, err := price.CalculateVolume(tradeAmount.MustQuantity())
  if err != nil {
   return reject.NewSingleItemList(
    reject.CodeOrderValueCalculationFailed,
    p.Name(),
    "order value calculation failed",
    "price and quantity could not be used to evaluate notional",
    reject.ScopeOrder,
   )
  }
  requestedNotional = notional
 }

 if requestedNotional.Compare(p.MaxAbsNotional) > 0 {
  // Business validation failures should become explicit rejects.
  return reject.NewSingleItemList(
   reject.CodeRiskLimitExceeded,
   p.Name(),
   "strategy cap exceeded",
   fmt.Sprintf(
    "requested notional %v, max allowed: %v",
    requestedNotional, p.MaxAbsNotional,
   ),
   reject.ScopeOrder,
  )
 }

 // This policy only validates. It does not reserve mutable state.
 return nil
}

func (p *NotionalCapPolicy) ApplyExecutionReport(
 pretrade.PostTradeContext,
 model.ExecutionReport,
 pretrade.PostTradeAdjustments,
 pretrade.PostTradePnls,
) []reject.AccountBlock {
 return nil
}

func (p *NotionalCapPolicy) ApplyAccountAdjustment(
 accountadjustment.Context,
 param.AccountID,
 model.AccountAdjustment,
 tx.Mutations,
 pretrade.AccountOutcomes,
) (pretrade.PolicyAccountAdjustmentResult, []reject.Reject) {
 return pretrade.PolicyAccountAdjustmentResult{}, nil
}
```

</details>

<details>
<summary>Python</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/python/tests/integration/test_examples_wiki.py -->
```python
import openpit


class NotionalCapPolicy(openpit.pretrade.Policy):
    def __init__(self, max_abs_notional: openpit.param.Volume) -> None:
        # Policy-local config: reject any order above this absolute notional.
        self._max_abs_notional = max_abs_notional

    @property
    def name(self) -> str:
        return "NotionalCapPolicy"

    def perform_pre_trade_check(
        self,
        ctx: openpit.pretrade.Context,
        order: openpit.Order,
    ) -> openpit.pretrade.PolicyPreTradeResult:
        assert order.operation is not None

        # Translate the public order surface into one number that this policy
        # can reason about: requested notional.
        trade_amount = order.operation.trade_amount
        if trade_amount.is_volume:
            requested_notional = trade_amount.as_volume
        else:
            assert trade_amount.is_quantity
            assert order.operation.price is not None
            requested_notional = order.operation.price.calculate_volume(
                trade_amount.as_quantity
            )

        if requested_notional > self._max_abs_notional:
            # Business validation failures should become explicit rejects,
            # not exceptions.
            return openpit.pretrade.PolicyPreTradeResult.reject(
                rejects=[
                    openpit.pretrade.PolicyReject(
                        code=openpit.pretrade.RejectCode.RISK_LIMIT_EXCEEDED,
                        reason="strategy cap exceeded",
                        details=(
                            "requested notional "
                            f"{requested_notional}, "
                            f"max allowed: {self._max_abs_notional}"
                        ),
                        scope=openpit.pretrade.RejectScope.ORDER,
                    )
                ]
            )

        # This policy only validates. It does not reserve mutable state.
        return openpit.pretrade.PolicyPreTradeResult.accept()

    def apply_execution_report(
        self,
        ctx: openpit.pretrade.PostTradeContext,
        report: openpit.ExecutionReport,
    ) -> openpit.pretrade.PostTradeResult | None:
        _ = ctx, report
        return None
```

</details>

<details>
<summary>JavaScript</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/js/tests/examples_wiki.test.ts -->
```ts
import { Order } from "@openpit/engine/model";
import { Volume } from "@openpit/engine/param";
import {
  type Context,
  type Policy,
  type PolicyPreTradeResult,
  type PolicyReject,
} from "@openpit/engine/pretrade";

// Reject any order above this absolute notional. Implemented against the
// public `Policy` interface; the callbacks read the typed `Order` view.
function notionalCapPolicy(maxAbsNotional: Volume): Policy {
  const name = "NotionalCapPolicy";
  return {
    name,

    checkPreTradeStart(ctx: Context, order: Order): Iterable<PolicyReject> {
      void ctx;
      void order;
      return [];
    },

    performPreTradeCheck(
      ctx: Context,
      order: Order,
    ): PolicyPreTradeResult | null {
      void ctx;
      const operation = order.operation;
      if (operation === undefined) {
        return {
          rejects: [
            {
              code: "MissingRequiredField",
              reason: "required order field missing",
              details: "operation is not set",
              scope: "order",
            },
          ],
        };
      }

      // Translate the public order surface into one number this policy can
      // reason about: requested notional.
      const tradeAmount = operation.tradeAmount;
      if (tradeAmount === undefined) {
        return {
          rejects: [
            {
              code: "MissingRequiredField",
              reason: "required order field missing",
              details: "tradeAmount is not set",
              scope: "order",
            },
          ],
        };
      }

      let requestedNotional: Volume;
      if (tradeAmount.isVolume) {
        requestedNotional = tradeAmount.asVolume!;
      } else {
        const price = operation.price;
        if (price === undefined) {
          return {
            rejects: [
              {
                code: "OrderValueCalculationFailed",
                reason: "order value calculation failed",
                details: "price not provided for evaluating notional",
                scope: "order",
              },
            ],
          };
        }
        requestedNotional = price.calculateVolume(tradeAmount.asQuantity!);
      }

      if (requestedNotional.compare(maxAbsNotional) > 0) {
        // Business validation failures should become explicit rejects.
        return {
          rejects: [
            {
              code: "RiskLimitExceeded",
              reason: "strategy cap exceeded",
              details: `requested notional ${requestedNotional.toString()}, max allowed: ${maxAbsNotional.toString()}`,
              scope: "order",
            },
          ],
        };
      }

      // This policy only validates. It does not reserve mutable state.
      return null;
    },
  };
}
```

</details>

<details>
<summary>C++</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/cpp/test/wiki/policy_api_test.cpp -->
```cpp
#include <openpit/openpit.hpp>

// Computes settlement notional with the exact domain value types. A boundary
// failure is an API error and therefore propagates as `openpit::Error`.
[[nodiscard]] openpit::param::Volume CalculateNotional(
    const openpit::param::Price& price,
    const openpit::param::Quantity& quantity) {
  return price.CalculateVolume(quantity);
}

class NotionalCapPolicy {
 public:
  // Policy-local config: reject any order above this absolute notional.
  explicit NotionalCapPolicy(openpit::param::Volume maxAbsNotional)
      : m_maxAbsNotional(maxAbsNotional) {}

  [[nodiscard]] std::string_view Name() const noexcept {
    return "NotionalCapPolicy";
  }

  void PerformPreTradeCheck(const openpit::model::Order& order,
                            const openpit::pretrade::Context& context,
                            openpit::tx::Mutations& mutations,
                            openpit::pretrade::Result& result,
                            openpit::pretrade::PolicyDecision& decision) const {
    static_cast<void>(context);
    static_cast<void>(mutations);
    static_cast<void>(result);
    if (!order.operation.has_value()) {
      openpit::pretrade::PushReject(
          decision,
          openpit::pretrade::Reject(
              std::string(Name()), openpit::pretrade::RejectScope::Order,
              openpit::pretrade::RejectCode::MissingRequiredField,
              "required order field missing", "operation is not set"));
      return;
    }
    const openpit::model::OrderOperation& operation = *order.operation;

    // Translate the public order surface into one number that this policy can
    // reason about: requested notional.
    if (!operation.tradeAmount.has_value()) {
      openpit::pretrade::PushReject(
          decision,
          openpit::pretrade::Reject(
              std::string(Name()), openpit::pretrade::RejectScope::Order,
              openpit::pretrade::RejectCode::MissingRequiredField,
              "required order field missing", "trade_amount is not set"));
      return;
    }
    const openpit::model::TradeAmount& tradeAmount = *operation.tradeAmount;

    // A volume trade amount is already the notional; a quantity trade amount
    // must be priced into a notional (notional = price * quantity).
    std::optional<openpit::param::Volume> requestedNotional =
        tradeAmount.AsVolume();
    if (!requestedNotional.has_value()) {
      const std::optional<openpit::param::Quantity> quantity =
          tradeAmount.AsQuantity();
      if (!operation.price.has_value()) {
        openpit::pretrade::PushReject(
            decision,
            openpit::pretrade::Reject(
                std::string(Name()), openpit::pretrade::RejectScope::Order,
                openpit::pretrade::RejectCode::OrderValueCalculationFailed,
                "order value calculation failed",
                "price not provided for evaluating notional"));
        return;
      }
      requestedNotional = CalculateNotional(*operation.price, *quantity);
    }

    if (*requestedNotional > m_maxAbsNotional) {
      // Business validation failures should become explicit rejects.
      openpit::pretrade::PushReject(
          decision,
          openpit::pretrade::Reject(
              std::string(Name()), openpit::pretrade::RejectScope::Order,
              openpit::pretrade::RejectCode::RiskLimitExceeded,
              "strategy cap exceeded",
              "requested notional " + requestedNotional->ToString() +
                  ", max allowed: " + m_maxAbsNotional.ToString()));
      return;
    }

    // This policy only validates. It does not reserve mutable state.
  }

  [[nodiscard]] std::vector<openpit::accounts::AccountBlock>
  ApplyExecutionReport(
      const openpit::pretrade::PostTradeContext& context,
      const openpit::ExecutionReport& report,
      openpit::pretrade::PostTradeAdjustments& adjustments,
      openpit::pretrade::PostTradePnls& pnls) const {
    static_cast<void>(context);
    static_cast<void>(report);
    static_cast<void>(adjustments);
    static_cast<void>(pnls);
    return {};
  }

 private:
  openpit::param::Volume m_maxAbsNotional;
};
```

</details>

<details>
<summary>Rust</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/crates/openpit/tests/examples_wiki.rs -->
```rust
use openpit::param::{TradeAmount, Volume};
use openpit::pretrade::{
    PolicyPreTradeResult, PostTradeContext, PreTradeContext, PreTradePolicy, Reject,
    RejectCode, RejectScope, Rejects,
};
use openpit::Mutations;
use openpit::{HasOrderPrice, HasTradeAmount};

struct NotionalCapPolicy {
    // Policy-local config: reject any order above this absolute notional.
    max_abs_notional: Volume,
}

impl<O, R, A, Sync> PreTradePolicy<O, R, A, Sync> for NotionalCapPolicy
where
    O: HasTradeAmount + HasOrderPrice,
    Sync: openpit::SyncMode,
{
    fn name(&self) -> &str {
        "NotionalCapPolicy"
    }

    fn perform_pre_trade_check(
        &self,
        _ctx: &PreTradeContext<<Sync as openpit::SyncMode>::StorageLockingPolicyFactory>,
        order: &O,
        _mutations: &mut Mutations,
    ) -> Result<Option<PolicyPreTradeResult>, Rejects> {
        // Translate the public order surface into one number that this policy
        // can reason about: requested notional.
        let trade_amount = match order.trade_amount() {
            Ok(trade_amount) => trade_amount,
            Err(error) => {
                return Err(Rejects::from(Reject::new(
                    <Self as PreTradePolicy<O, R, A, Sync>>::name(self),
                    RejectScope::Order,
                    RejectCode::MissingRequiredField,
                    "required order field missing",
                    error.to_string(),
                )));
            }
        };
        let price = match order.price() {
            Ok(price) => price,
            Err(error) => {
                return Err(Rejects::from(Reject::new(
                    <Self as PreTradePolicy<O, R, A, Sync>>::name(self),
                    RejectScope::Order,
                    RejectCode::MissingRequiredField,
                    "required order field missing",
                    error.to_string(),
                )));
            }
        };
        let requested_notional = match (trade_amount, price) {
            (TradeAmount::Volume(volume), _) => volume,
            (TradeAmount::Quantity(quantity), Some(price)) => {
                match price.calculate_volume(quantity) {
                    Ok(v) => v,
                    Err(_) => {
                        return Err(Rejects::from(Reject::new(
                            <Self as PreTradePolicy<O, R, A, Sync>>::name(self),
                            RejectScope::Order,
                            RejectCode::OrderValueCalculationFailed,
                            "order value calculation failed",
                            "price and quantity could not be used to evaluate notional",
                        )));
                    }
                }
            }
            (TradeAmount::Quantity(_), None) => {
                return Err(Rejects::from(Reject::new(
                    <Self as PreTradePolicy<O, R, A, Sync>>::name(self),
                    RejectScope::Order,
                    RejectCode::OrderValueCalculationFailed,
                    "order value calculation failed",
                    "price not provided for evaluating cash flow/notional/volume",
                )));
            }
            _ => {
                return Err(Rejects::from(Reject::new(
                    <Self as PreTradePolicy<O, R, A, Sync>>::name(self),
                    RejectScope::Order,
                    RejectCode::UnsupportedOrderType,
                    "unsupported order type",
                    "custom trade amount variant is not supported by this policy",
                )));
            }
        };

        if requested_notional > self.max_abs_notional {
            // Business validation failures should become explicit rejects.
            return Err(Rejects::from(Reject::new(
                <Self as PreTradePolicy<O, R, A, Sync>>::name(self),
                RejectScope::Order,
                RejectCode::RiskLimitExceeded,
                "strategy cap exceeded",
                format!(
                    "requested notional {}, max allowed: {}",
                    requested_notional, self.max_abs_notional
                ),
            )));
        }
        Ok(None)
    }

    fn apply_execution_report(
        &self,
        _ctx: &PostTradeContext<<Sync as openpit::SyncMode>::StorageLockingPolicyFactory>,
        _report: &R,
    ) -> Option<openpit::PostTradeResult> {
        None
    }
}
```

</details>

## Rollback on Main-Stage Error

If at least one main-stage policy rejects, the engine does not return a
reservation and rolls back all registered mutations in reverse order.

Rollback order is deterministic:

- registration order for commit
- reverse registration order for rollback

### Example: Rollback Safety Pattern

This pattern is useful when one policy updates intermediate in-memory state and
the same policy decides that the request must be rejected.

<details>
<summary>Go</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/go/examples_wiki_test.go -->
```go
package main

import (
 "fmt"

 "go.openpit.dev/openpit/accountadjustment"
 "go.openpit.dev/openpit/model"
 "go.openpit.dev/openpit/param"
 "go.openpit.dev/openpit/pretrade"
 "go.openpit.dev/openpit/reject"
 "go.openpit.dev/openpit/tx"
)

type ReserveThenValidatePolicy struct {
 reserved param.Volume
 limit    param.Volume
}

func (p *ReserveThenValidatePolicy) Close() {}

func (p *ReserveThenValidatePolicy) Name() string {
 return "ReserveThenValidatePolicy"
}

func (p *ReserveThenValidatePolicy) PolicyGroupID() model.PolicyGroupID {
 return model.DefaultPolicyGroupID
}

func (p *ReserveThenValidatePolicy) CheckPreTradeStart(
 pretrade.Context,
 model.Order,
) []reject.Reject {
 return nil
}

func (p *ReserveThenValidatePolicy) PerformPreTradeCheck(
 _ pretrade.Context,
 _ model.Order,
 mutations tx.Mutations,
 _ pretrade.Result,
) []reject.Reject {
 // Pretend that this request needs a temporary reservation of 100.
 // We apply it eagerly because downstream logic wants to observe the
 // tentative state immediately.
 prevReserved := p.reserved
 nextReserved, _ := param.NewVolumeFromString("100")
 p.reserved = nextReserved

 _ = mutations.Push(
  func() {
   // Commit is empty: state was applied eagerly.
  },
  func() {
   p.reserved = prevReserved
  },
 )

 if p.reserved.Compare(p.limit) > 0 {
  // Return the reject after the rollback mutation is registered.
  // The engine will restore the previous state automatically.
  return reject.NewSingleItemList(
   reject.CodeRiskLimitExceeded,
   p.Name(),
   "temporary reservation exceeds limit",
   fmt.Sprintf("reserved %v, limit: %v", nextReserved, p.limit),
   reject.ScopeOrder,
  )
 }

 return nil
}

func (p *ReserveThenValidatePolicy) ApplyExecutionReport(
 pretrade.PostTradeContext,
 model.ExecutionReport,
 pretrade.PostTradeAdjustments,
 pretrade.PostTradePnls,
) []reject.AccountBlock {
 return nil
}

func (p *ReserveThenValidatePolicy) ApplyAccountAdjustment(
 accountadjustment.Context,
 param.AccountID,
 model.AccountAdjustment,
 tx.Mutations,
 pretrade.AccountOutcomes,
) (pretrade.PolicyAccountAdjustmentResult, []reject.Reject) {
 return pretrade.PolicyAccountAdjustmentResult{}, nil
}
```

</details>

<details>
<summary>Python</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/python/tests/integration/test_examples_wiki.py -->
```python
import openpit


class ReserveThenValidatePolicy(openpit.pretrade.Policy):
    def __init__(self) -> None:
        self._reserved = openpit.param.Volume(0.0)
        self._limit = openpit.param.Volume(50.0)

    @property
    def name(self) -> str:
        return "ReserveThenValidatePolicy"

    def perform_pre_trade_check(
        self,
        ctx: openpit.pretrade.Context,
        order: openpit.Order,
    ) -> openpit.pretrade.PolicyPreTradeResult:
        assert order.operation is not None

        # Pretend that this request needs a temporary reservation of 100.
        # We apply it eagerly because downstream logic wants to observe the
        # tentative state immediately.
        prev_reserved = self._reserved
        next_reserved = openpit.param.Volume(100.0)
        self._reserved = next_reserved

        rollback = openpit.Mutation(
            commit=lambda: None,  # Commit is empty: state was applied eagerly.
            rollback=lambda: setattr(self, "_reserved", prev_reserved),
        )

        if next_reserved > self._limit:
            # Return the reject together with the rollback mutation.
            # The engine will restore the previous state automatically.
            return openpit.pretrade.PolicyPreTradeResult.reject(
                rejects=[
                    openpit.pretrade.PolicyReject(
                        code=openpit.pretrade.RejectCode.RISK_LIMIT_EXCEEDED,
                        reason="temporary reservation exceeds limit",
                        details=(
                            f"reserved {next_reserved}, "
                            f"limit: {self._limit}"
                        ),
                        scope=openpit.pretrade.RejectScope.ORDER,
                    )
                ],
                mutations=[rollback],
            )

        return openpit.pretrade.PolicyPreTradeResult.accept(mutations=[rollback])

    def apply_execution_report(
        self,
        ctx: openpit.pretrade.PostTradeContext,
        report: openpit.ExecutionReport,
    ) -> openpit.pretrade.PostTradeResult | None:
        _ = ctx, report
        return None
```

</details>

<details>
<summary>JavaScript</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/js/tests/examples_wiki.test.ts -->
```ts
import { Order } from "@openpit/engine/model";
import { Volume } from "@openpit/engine/param";
import {
  type Context,
  type Policy,
  type PolicyPreTradeResult,
} from "@openpit/engine/pretrade";

// Updates intermediate in-memory state and may then reject the same request.
function reserveThenValidatePolicy(): Policy {
  // Policy-local state, captured by the hook closure.
  let reserved = Volume.fromString("0");
  const limit = Volume.fromString("50");

  return {
    name: "ReserveThenValidatePolicy",

    checkPreTradeStart() {
      return [];
    },

    performPreTradeCheck(
      ctx: Context,
      order: Order,
    ): PolicyPreTradeResult | null {
      void ctx;
      void order;

      // Pretend that this request needs a temporary reservation of 100. We
      // apply it eagerly because downstream logic wants to observe the
      // tentative state immediately.
      const prevReserved = reserved;
      const nextReserved = Volume.fromString("100");
      reserved = nextReserved;

      // Commit is empty: state was applied eagerly. Rollback restores the
      // previous value if any policy rejects; the engine runs it
      // automatically in reverse registration order.
      const rollback = {
        commit: () => {},
        rollback: () => {
          reserved = prevReserved;
        },
      };

      if (nextReserved.compare(limit) > 0) {
        // Return the reject together with the rollback mutation.
        return {
          rejects: [
            {
              code: "RiskLimitExceeded",
              reason: "temporary reservation exceeds limit",
              details: `reserved ${nextReserved.toString()}, limit: ${limit.toString()}`,
              scope: "order",
            },
          ],
          mutations: [rollback],
        };
      }

      return { mutations: [rollback] };
    },
  };
}
```

</details>

<details>
<summary>C++</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/cpp/test/wiki/policy_api_test.cpp -->
```cpp
#include <openpit/openpit.hpp>

#include <memory>
#include <string_view>

class ReserveThenValidatePolicy {
 public:
  ReserveThenValidatePolicy() = default;

  [[nodiscard]] std::string_view Name() const noexcept {
    return "ReserveThenValidatePolicy";
  }

  void PerformPreTradeCheck(const openpit::model::Order& order,
                            const openpit::pretrade::Context& context,
                            openpit::tx::Mutations& mutations,
                            openpit::pretrade::Result& result,
                            openpit::pretrade::PolicyDecision& decision) const {
    static_cast<void>(order);
    static_cast<void>(context);
    static_cast<void>(mutations);
    static_cast<void>(result);

    // Pretend that this request needs a temporary reservation of 100. We apply
    // it eagerly because downstream logic wants to observe the tentative state
    // immediately.
    const openpit::param::Volume prevReserved = m_reserved;
    const openpit::param::Volume nextReserved =
        openpit::param::Volume::FromString("100");
    m_reserved = nextReserved;

    if (m_reserved > m_limit) {
      // The decision is rejected, so the engine will not apply this request:
      // restore the previous state before returning the reject.
      m_reserved = prevReserved;
      openpit::pretrade::PushReject(
          decision,
          openpit::pretrade::Reject(
              std::string(Name()), openpit::pretrade::RejectScope::Order,
              openpit::pretrade::RejectCode::RiskLimitExceeded,
              "temporary reservation exceeds limit",
              "reserved " + nextReserved.ToString() +
                  ", limit: " + m_limit.ToString()));
    }
  }

  [[nodiscard]] std::vector<openpit::accounts::AccountBlock>
  ApplyExecutionReport(
      const openpit::pretrade::PostTradeContext& context,
      const openpit::ExecutionReport& report,
      openpit::pretrade::PostTradeAdjustments& adjustments,
      openpit::pretrade::PostTradePnls& pnls) const {
    static_cast<void>(context);
    static_cast<void>(report);
    static_cast<void>(adjustments);
    static_cast<void>(pnls);
    return {};
  }

 private:
  mutable openpit::param::Volume m_reserved =
      openpit::param::Volume::FromString("0");
  openpit::param::Volume m_limit =
      openpit::param::Volume::FromString("50");
};
```

</details>

<details>
<summary>Rust</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/crates/openpit/tests/examples_wiki.rs -->
```rust
use std::cell::RefCell;
use std::rc::Rc;

use openpit::param::Volume;
use openpit::pretrade::{
    PolicyPreTradeResult, PostTradeContext, PreTradeContext, PreTradePolicy, Reject,
    RejectCode, RejectScope, Rejects,
};
use openpit::{Mutation, Mutations};

struct ReserveThenValidatePolicy {
    reserved: Rc<RefCell<Volume>>,
    next: Volume,
    limit: Volume,
}

impl<O, R, A, Sync> PreTradePolicy<O, R, A, Sync> for ReserveThenValidatePolicy
where
    Sync: openpit::SyncMode,
{
    fn name(&self) -> &str {
        "ReserveThenValidatePolicy"
    }

    fn perform_pre_trade_check(
        &self,
        _ctx: &PreTradeContext<<Sync as openpit::SyncMode>::StorageLockingPolicyFactory>,
        _order: &O,
        mutations: &mut Mutations,
    ) -> Result<Option<PolicyPreTradeResult>, Rejects> {
        let prev = *self.reserved.borrow();
        let rollback_reserved = Rc::clone(&self.reserved);
        let next = self.next;
        *self.reserved.borrow_mut() = next;

        mutations.push(Mutation::new(
            || {
                // Commit is empty: state was applied eagerly.
            },
            move || {
                *rollback_reserved.borrow_mut() = prev;
            },
        ));

        if next > self.limit {
            return Err(Rejects::from(Reject::new(
                <Self as PreTradePolicy<O, R, A, Sync>>::name(self),
                RejectScope::Order,
                RejectCode::RiskLimitExceeded,
                "temporary reservation exceeds limit",
                format!("reserved {}, limit: {}", next, self.limit),
            )));
        }
        Ok(None)
    }

    fn apply_execution_report(
        &self,
        _ctx: &PostTradeContext<<Sync as openpit::SyncMode>::StorageLockingPolicyFactory>,
        _report: &R,
    ) -> Option<openpit::PostTradeResult> {
        None
    }
}
```

</details>

## Custom `Order` and `Execution Report` Models

### Go Custom Models

Go uses `ClientEngine` and typed policy interfaces to work with project-specific
order and report types:

- Embed `model.Order` into a custom struct to add project-specific fields.
- Embed `model.ExecutionReport` into a custom struct to add project-specific
  fields.
- Implement `pretrade.ClientPreTradePolicy[Order, Report]` - all four
  callbacks receive the typed project struct, not the generic `model.Order`;
  account adjustment uses `model.AccountAdjustment` regardless of client
  type.
- Build the engine with `NewClientPreTradeEngineBuilder[Order, Report]()`, which
  returns a `*ClientEngine[Order, Report, ...]`. The client engine wraps each
  submitted value in a cgo handle and routes it to the typed policy callbacks.

<details>
<summary>Go</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/go/examples_wiki_test.go -->
```go
package main

import (
 "fmt"
 "log"

 "go.openpit.dev/openpit"
 "go.openpit.dev/openpit/accountadjustment"
 "go.openpit.dev/openpit/model"
 "go.openpit.dev/openpit/param"
 "go.openpit.dev/openpit/pretrade"
 "go.openpit.dev/openpit/reject"
 "go.openpit.dev/openpit/tx"
)

// StrategyOrder carries project-specific metadata alongside the standard order.
type StrategyOrder struct {
 model.Order
 StrategyTag string
}

// StrategyReport carries project-specific metadata alongside
// the standard report.
type StrategyReport struct {
 model.ExecutionReport
 VenueExecID string
}

// StrategyTagPolicy rejects orders from blocked strategy tags.
type StrategyTagPolicy struct{}

func (*StrategyTagPolicy) Close() {}

func (*StrategyTagPolicy) Name() string { return "StrategyTagPolicy" }

func (*StrategyTagPolicy) PolicyGroupID() model.PolicyGroupID {
 return model.DefaultPolicyGroupID
}

func (p *StrategyTagPolicy) CheckPreTradeStart(
 _ pretrade.Context,
 order StrategyOrder,
) []reject.Reject {
 if order.StrategyTag == "blocked" {
  return reject.NewSingleItemList(
   reject.CodeComplianceRestriction,
   p.Name(),
   "strategy blocked",
   fmt.Sprintf("strategy tag %q is not allowed", order.StrategyTag),
   reject.ScopeOrder,
  )
 }
 return nil
}

func (*StrategyTagPolicy) PerformPreTradeCheck(
 pretrade.Context,
 StrategyOrder,
 tx.Mutations,
 pretrade.Result,
) []reject.Reject {
 return nil
}

func (*StrategyTagPolicy) ApplyExecutionReport(
 pretrade.PostTradeContext,
 StrategyReport,
 pretrade.PostTradeAdjustments,
 pretrade.PostTradePnls,
) []reject.AccountBlock {
 return nil
}

func (*StrategyTagPolicy) ApplyAccountAdjustment(
 accountadjustment.Context,
 param.AccountID,
 model.AccountAdjustment,
 tx.Mutations,
 pretrade.AccountOutcomes,
) (pretrade.PolicyAccountAdjustmentResult, []reject.Reject) {
 return pretrade.PolicyAccountAdjustmentResult{}, nil
}

func main() {
 engine, err := openpit.NewClientPreTradeEngineBuilder[
  StrategyOrder, StrategyReport,
 ]().
  FullSync().
  PreTrade(&StrategyTagPolicy{}).
  Build()
 if err != nil {
  log.Fatal(err)
 }
 defer engine.Stop()

 order := StrategyOrder{Order: model.NewOrder(), StrategyTag: "alpha"}
 request, rejects, err := engine.StartPreTrade(order)
 if err != nil {
  log.Fatal(err)
 }
 if rejects != nil {
  for _, r := range rejects {
   fmt.Printf("rejected by %s: %s\n", r.Policy, r.Reason)
  }
  return
 }
 defer request.Close()

 reservation, rejects, err := request.Execute()
 if err != nil {
  log.Fatal(err)
 }
 if rejects != nil {
  for _, r := range rejects {
   fmt.Printf("rejected by %s: %s\n", r.Policy, r.Reason)
  }
  return
 }
 defer reservation.Close()
 reservation.Commit()
}
```

</details>

### Python Custom Models

Python custom models inherit from `openpit.Order` or `openpit.ExecutionReport`.
The original subclass instance reaches policy callbacks unchanged. Policies
access project-specific attributes by casting the received base type.

<details>
<summary>Python</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/python/tests/integration/test_examples_wiki.py -->
```python
import typing

import openpit


class StrategyOrder(openpit.Order):
    def __init__(
        self,
        *,
        operation: openpit.OrderOperation,
        strategy_tag: str,
    ) -> None:
        super().__init__(operation=operation)
        # Project-specific metadata carried alongside the standard order fields.
        self.strategy_tag = strategy_tag


class StrategyReport(openpit.ExecutionReport):
    def __init__(
        self,
        *,
        operation: openpit.ExecutionReportOperation,
        financial_impact: openpit.FinancialImpact,
        venue_exec_id: str,
    ) -> None:
        super().__init__(operation=operation, financial_impact=financial_impact)
        # Project-specific metadata alongside the standard report fields.
        self.venue_exec_id = venue_exec_id


class StrategyTagPolicy(openpit.pretrade.Policy):
    def __init__(self) -> None:
        self.last_venue_exec_id: str | None = None

    @property
    def name(self) -> str:
        return "StrategyTagPolicy"

    def check_pre_trade_start(
        self,
        ctx: openpit.pretrade.Context,
        order: openpit.Order,
    ) -> list[openpit.pretrade.PolicyReject]:
        # The original subclass instance reaches the callback unchanged.
        strategy_order = typing.cast(StrategyOrder, order)
        if strategy_order.strategy_tag == "blocked":
            return [
                openpit.pretrade.PolicyReject(
                    code=openpit.pretrade.RejectCode.COMPLIANCE_RESTRICTION,
                    reason="strategy blocked",
                    details=(
                        "strategy tag "
                        f"{strategy_order.strategy_tag!r}"
                        " is not allowed"
                    ),
                    scope=openpit.pretrade.RejectScope.ORDER,
                )
            ]
        return []

    def apply_execution_report(
        self,
        ctx: openpit.pretrade.PostTradeContext,
        report: openpit.ExecutionReport,
    ) -> openpit.pretrade.PostTradeResult | None:
        del ctx
        strategy_report = typing.cast(StrategyReport, report)
        self.last_venue_exec_id = strategy_report.venue_exec_id
        return None


policy = StrategyTagPolicy()
engine = (
    openpit.Engine.builder()
    .no_sync()
    .pre_trade(policy)
    .build()
)

order = StrategyOrder(
    operation=openpit.OrderOperation(
        instrument=openpit.Instrument("AAPL", "USD"),
        account_id=openpit.param.AccountId.from_int(99224416),
        side=openpit.param.Side.BUY,
        trade_amount=openpit.param.TradeAmount.quantity(10),
        price=openpit.param.Price(25),
    ),
    strategy_tag="alpha",
)

start_result = engine.start_pre_trade(order=order)
if not start_result:
    messages = ", ".join(
        f"{r.policy} [{r.code}]: {r.reason}: {r.details}"
        for r in start_result.rejects
    )
    raise RuntimeError(messages)

execute_result = start_result.request.execute()
if not execute_result:
    messages = ", ".join(
        f"{r.policy} [{r.code}]: {r.reason}: {r.details}"
        for r in execute_result.rejects
    )
    raise RuntimeError(messages)

execute_result.reservation.commit()

order.strategy_tag = "blocked"
blocked = engine.start_pre_trade(order=order)
assert not blocked
assert blocked.rejects[0].code == openpit.pretrade.RejectCode.COMPLIANCE_RESTRICTION

report = StrategyReport(
    operation=openpit.ExecutionReportOperation(
        instrument=openpit.Instrument("AAPL", "USD"),
        account_id=openpit.param.AccountId.from_int(99224416),
        side=openpit.param.Side.BUY,
    ),
    financial_impact=openpit.FinancialImpact(
        pnl=openpit.param.Pnl(0),
        fee=openpit.param.Fee(0),
    ),
    venue_exec_id="venue-123",
)
engine.apply_execution_report(report=report)
assert policy.last_venue_exec_id == "venue-123"
```

</details>

### JS Custom Models

JavaScript composes host metadata with the OpenPit wrapper types. The engine
validates the standard `Order` / `ExecutionReport` fields separately, while each
custom policy callback receives a fresh clone that preserves the submitted
object's custom fields, prototype, symbols, and cycles. Parameterize `Policy`
with the application model types to keep those fields typed in callbacks.

<details>
<summary>JavaScript</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/js/tests/examples_wiki.test.ts -->
```ts
import { Engine } from "@openpit/engine";
import { ExecutionReport, Order } from "@openpit/engine/model";
import { TradeAmount } from "@openpit/engine/param";
import { type Policy } from "@openpit/engine/pretrade";

type StrategyOrder = Order & {
  strategyTag: string;
};

type StrategyReport = ExecutionReport & {
  venueExecId: string;
};

let appliedVenueExecId: string | undefined;
const strategyTagPolicy: Policy<StrategyOrder, StrategyReport> = {
  name: "StrategyTagPolicy",

  checkPreTradeStart(_ctx, order) {
    if (order.strategyTag === "blocked") {
      return [
        {
          code: "ComplianceRestriction",
          reason: "strategy blocked",
          details: `strategy tag ${order.strategyTag} is not allowed`,
          scope: "order",
        },
      ];
    }
    return [];
  },

  performPreTradeCheck() {
    return null;
  },

  applyExecutionReport(_ctx, report) {
    appliedVenueExecId = report.venueExecId;
    return null;
  },
};

const strategyOrder: StrategyOrder = Object.assign(new Order(), {
  strategyTag: "alpha",
});
strategyOrder.operation = {
  underlyingAsset: "AAPL",
  settlementAsset: "USD",
  accountId: 99_224_416n,
  side: "BUY",
  tradeAmount: TradeAmount.quantity("10"),
  price: "25",
};

const strategyReport: StrategyReport = Object.assign(new ExecutionReport(), {
  venueExecId: "venue-42",
});
strategyReport.operation = {
  underlyingAsset: "AAPL",
  settlementAsset: "USD",
  accountId: 99_224_416n,
  side: "BUY",
};
strategyReport.financialImpact = { pnl: "5", fee: "0.25" };

const engine = Engine.builder().preTrade(strategyTagPolicy).build();
const start = engine.startPreTrade(strategyOrder);
if (!start.ok) {
  throw new Error("strategy order must pass the start stage");
}

const request = start.request;
if (request === undefined) {
  throw new Error("accepted start result is missing its request");
}
const execute = request.execute();
if (!execute.ok) {
  throw new Error("strategy order must pass the main stage");
}
const reservation = execute.reservation;
if (reservation === undefined) {
  throw new Error("accepted execute result is missing its reservation");
}
reservation.commit();
engine.applyExecutionReport(strategyReport);

console.log(strategyOrder.strategyTag, appliedVenueExecId);
```

</details>

### C++ Custom Models

C++ custom models derive from `openpit::model::Order` or
`openpit::model::ExecutionReport` and add project-specific fields. A typed
policy receives the concrete type through the SafeSlow adapter, which recovers
it from the context order with a checked cast; the adapter then drives the
engine builder through `openpit::pretrade::CustomPolicy`.

<details>
<summary>C++</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/cpp/test/wiki/policy_api_test.cpp -->
```cpp
#include <openpit/openpit.hpp>

#include <string>

// StrategyOrder carries project-specific metadata alongside the standard order.
struct StrategyOrder : public openpit::model::Order {
  std::string strategyTag;
};

// StrategyReport carries project-specific metadata alongside the standard
// report.
struct StrategyReport : public openpit::model::ExecutionReport {
  std::string venueExecId;
};

// StrategyTagPolicy rejects orders from blocked strategy tags.
class StrategyTagPolicy {
 public:
  explicit StrategyTagPolicy(std::shared_ptr<std::string> appliedVenueExecId)
      : m_appliedVenueExecId(std::move(appliedVenueExecId)) {}

  [[nodiscard]] std::string_view Name() const noexcept {
    return "StrategyTagPolicy";
  }

  [[nodiscard]] std::optional<openpit::pretrade::Reject> CheckPreTradeStart(
      const StrategyOrder& order) const {
    if (order.strategyTag == "blocked") {
      return openpit::pretrade::Reject(
          std::string(Name()), openpit::pretrade::RejectScope::Order,
          openpit::pretrade::RejectCode::ComplianceRestriction,
          "strategy blocked",
          "strategy tag \"" + order.strategyTag + "\" is not allowed");
    }
    return std::nullopt;
  }

  [[nodiscard]] std::vector<openpit::accounts::AccountBlock>
  ApplyExecutionReport(
      const openpit::pretrade::PostTradeContext& context,
      const StrategyReport& report,
      openpit::pretrade::PostTradeAdjustments& adjustments,
      openpit::pretrade::PostTradePnls& pnls) const {
    static_cast<void>(context);
    static_cast<void>(adjustments);
    static_cast<void>(pnls);
    *m_appliedVenueExecId = report.venueExecId;
    return {};
  }

 private:
  std::shared_ptr<std::string> m_appliedVenueExecId;
};

using StrategyStartAdapter =
    openpit::pretrade::StartPolicyAdapterWithSafeSlowArgType<StrategyTagPolicy,
                                                             StrategyOrder,
                                                             StrategyReport>;

const auto appliedVenueExecId = std::make_shared<std::string>();
openpit::pretrade::CustomPolicy<StrategyStartAdapter> policy(
    "StrategyTagPolicy",
    StrategyStartAdapter{StrategyTagPolicy{appliedVenueExecId}});

openpit::EngineBuilder builder(openpit::SyncPolicy::Full);
builder.Add(policy);
openpit::Engine engine = builder.Build();

StrategyOrder order;
openpit::model::OrderOperation op;
op.instrument = openpit::model::Instrument(::openpit::param::Asset("AAPL"),
                                           ::openpit::param::Asset("USD"));
op.accountId = openpit::param::AccountId::FromUint64(99224416);
op.side = openpit::model::Side::Buy;
op.tradeAmount =
    openpit::model::TradeAmount::OfQuantity(
        openpit::param::Quantity::FromString("10"));
op.price = openpit::param::Price::FromString("25");
order.operation = std::move(op);
order.strategyTag = "alpha";

openpit::pretrade::StartResult start = engine.StartPreTrade(order);
assert(start.Passed());

openpit::pretrade::ExecuteResult execute = start.request->Execute();
assert(execute.Passed());
execute.reservation->Commit();

StrategyReport report;
openpit::model::ExecutionReportOperation reportOp;
reportOp.instrument = openpit::model::Instrument(
    ::openpit::param::Asset("AAPL"), ::openpit::param::Asset("USD"));
reportOp.accountId = openpit::param::AccountId::FromUint64(99224416);
reportOp.side = openpit::model::Side::Buy;
report.operation = std::move(reportOp);
report.venueExecId = "venue-42";

const openpit::PostTradeResult post = engine.ApplyExecutionReport(report);
assert(post.accountBlocks.empty());
assert(*appliedVenueExecId == "venue-42");
```

</details>

### Rust Custom Models

Rust uses capability traits (`Has*`) and can compose `OrderOperation` with
project-only fields plus `Deref` to inherit required capabilities.

<details>
<summary>Rust</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/crates/openpit/tests/examples_wiki.rs -->
```rust
use std::ops::Deref;

use openpit::{ExecutionReportOperation, OrderOperation};

struct StrategyOrder {
    inner: OrderOperation,
    strategy_tag: String,
}

impl Deref for StrategyOrder {
    type Target = OrderOperation;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

struct StrategyReport {
    inner: ExecutionReportOperation,
    venue_exec_id: String,
}

impl Deref for StrategyReport {
    type Target = ExecutionReportOperation;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}
```

</details>

See [Custom Rust Types](Custom-Rust-Types.md) for the available derive setup,
manual trait implementations, and wrapper-composition patterns.

## Blocking an Account from a Policy

An account-adjustment policy can accept a modification and report an account
block in the same callback result. The engine commits the accepted batch, then
records every reported block before `apply account adjustments` returns. Thus a
caller that receives an account block already has a blocked account: every
later start stage for it is rejected with `ACCOUNT BLOCKED`, without involving
any policy start-check.

Pre-trade contexts still expose an optional `account control` handle when the
engine provides the account-block facility for an order's account. A pre-trade
policy may use that handle immediately or from a mutation commit closure. An
account-adjustment policy reports its block through its return value instead;
it does not mutate account blocking as an unreported callback side effect.

### Language Surfaces

- Go: return `pretrade.PolicyAccountAdjustmentResult` with
  `AccountBlocks: []reject.AccountBlock{...}` and no rejects.
- Python: return
  `openpit.pretrade.PolicyAccountAdjustmentResult(account_blocks=(openpit.pretrade.AccountBlock(...),))`.
- JavaScript: return a `PolicyAccountAdjustmentResult` object with an
  `accountBlocks` array.
- C++: return `openpit::pretrade::PolicyAccountAdjustmentResult` and append an
  `accounts::AccountBlock` to `accountBlocks`.
- Rust: return `Ok(PolicyAccountAdjustmentResult { account_adjustments: Vec::new(), account_blocks: vec![AccountBlock::new(...)] })`.

### Example: Block an Account from an Adjustment Callback

<details>
<summary>Go</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/go/examples_wiki_test.go -->
```go
type BlockOnAdjustmentPolicy struct{}

func (*BlockOnAdjustmentPolicy) Close() {}

func (*BlockOnAdjustmentPolicy) Name() string {
 return "BlockOnAdjustmentPolicy"
}

func (*BlockOnAdjustmentPolicy) PolicyGroupID() model.PolicyGroupID {
 return model.DefaultPolicyGroupID
}

func (*BlockOnAdjustmentPolicy) CheckPreTradeStart(
 pretrade.Context,
 model.Order,
) []reject.Reject {
 return nil
}

func (*BlockOnAdjustmentPolicy) PerformPreTradeCheck(
 pretrade.Context,
 model.Order,
 tx.Mutations,
 pretrade.Result,
) []reject.Reject {
 return nil
}

func (*BlockOnAdjustmentPolicy) ApplyExecutionReport(
 pretrade.PostTradeContext,
 model.ExecutionReport,
 pretrade.PostTradeAdjustments,
 pretrade.PostTradePnls,
) []reject.AccountBlock {
 return nil
}

func (p *BlockOnAdjustmentPolicy) ApplyAccountAdjustment(
 _ accountadjustment.Context,
 _ param.AccountID,
 _ model.AccountAdjustment,
 _ tx.Mutations,
 _ pretrade.AccountOutcomes,
) (pretrade.PolicyAccountAdjustmentResult, []reject.Reject) {
 return pretrade.PolicyAccountAdjustmentResult{
  AccountBlocks: []reject.AccountBlock{reject.NewAccountBlock(
   reject.CodeAccountBlocked,
   p.Name(),
   "blocked by account-adjustment policy",
   "custom policy reported an account block from a callback",
  )},
 }, nil
}
```

</details>

<details>
<summary>Python</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/python/tests/integration/test_examples_wiki.py -->
```python
import openpit


class BlockOnAdjustmentPolicy(openpit.pretrade.Policy):
    @property
    def name(self) -> str:
        return "BlockOnAdjustmentPolicy"

    def apply_account_adjustment(
        self,
        _ctx: openpit.AccountAdjustmentContext,
        account_id: openpit.param.AccountId,
        adjustment: openpit.AccountAdjustment,
    ) -> openpit.pretrade.PolicyAccountAdjustmentResult:
        del account_id, adjustment
        return openpit.pretrade.PolicyAccountAdjustmentResult(
            account_blocks=(
                openpit.pretrade.AccountBlock(
                    policy=self.name,
                    code=openpit.pretrade.RejectCode.ACCOUNT_BLOCKED,
                    reason="blocked by account-adjustment policy",
                    details="custom policy reported an account block from a callback",
                ),
            ),
        )


engine = (
    openpit.Engine.builder().no_sync().pre_trade(policy=BlockOnAdjustmentPolicy()).build()
)

# The accepted adjustment reports a block that the engine has already recorded.
result = engine.apply_account_adjustment(
    account_id=openpit.param.AccountId.from_int(99224416),
    adjustments=[
        openpit.AccountAdjustment(
            operation=openpit.AccountAdjustmentBalanceOperation(asset="USD")
        )
    ],
)
assert result.ok
assert len(result.account_blocks) == 1

# A later order on the same account is rejected with ACCOUNT_BLOCKED, without
# any start-check involvement.
blocked = engine.start_pre_trade(
    order=openpit.Order(
        operation=openpit.OrderOperation(
            instrument=openpit.Instrument("AAPL", "USD"),
            account_id=openpit.param.AccountId.from_int(99224416),
            side=openpit.param.Side.BUY,
            trade_amount=openpit.param.TradeAmount.quantity(10),
            price=openpit.param.Price(25),
        ),
    )
)
assert not blocked.ok
assert blocked.rejects[0].code == openpit.pretrade.RejectCode.ACCOUNT_BLOCKED
```

</details>

<details>
<summary>JavaScript</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/js/tests/examples_wiki.test.ts -->
```ts
import { Engine } from "@openpit/engine";
import { type AccountAdjustmentContext } from "@openpit/engine/accountadjustment";
import { type AccountAdjustment } from "@openpit/engine/model";
import { AccountId, TradeAmount } from "@openpit/engine/param";
import {
  type Policy,
  type PolicyAccountAdjustmentResult,
} from "@openpit/engine/pretrade";
import { AccountBlock } from "@openpit/engine/reject";

const blockOnAdjustmentPolicy: Policy = {
  name: "BlockOnAdjustmentPolicy",

  checkPreTradeStart() {
    return [];
  },

  performPreTradeCheck() {
    return null;
  },

  applyAccountAdjustment(
    _ctx: AccountAdjustmentContext,
    _accountId: AccountId,
    _adjustment: AccountAdjustment,
  ): PolicyAccountAdjustmentResult {
    return {
      accountBlocks: [new AccountBlock(
        "BlockOnAdjustmentPolicy",
        "AccountBlocked",
        "blocked by account-adjustment policy",
        "custom policy reported an account block from a callback",
      )],
    };
  },
};

const engine = Engine.builder()
  .preTrade(blockOnAdjustmentPolicy)
  .build();

// The accepted adjustment reports a block that the engine has already recorded.
const adjustmentResult = engine.applyAccountAdjustment(99224416, [
  { operation: { asset: "USD" } },
]);
if (!adjustmentResult.ok || adjustmentResult.accountBlocks.length !== 1) {
  throw new Error("accepted adjustment must report one account block");
}

// A later order on the same account is rejected with AccountBlocked, without
// any start-check involvement.
const blocked = engine.startPreTrade({
  operation: {
    underlyingAsset: "AAPL",
    settlementAsset: "USD",
    accountId: 99224416,
    side: "BUY",
    tradeAmount: TradeAmount.quantity("10"),
    price: "25",
  },
});
if (blocked.ok) {
  throw new Error("order must be blocked");
}
if (blocked.rejects[0]!.code !== "AccountBlocked") {
  throw new Error("expected AccountBlocked");
}
```

</details>

In C++, the account-adjustment hook returns the block in its policy result.
The outcome is the same as the other bindings: before the accepted batch result
is returned, the engine records the block, and every later start stage for that
account is rejected with `ACCOUNT_BLOCKED` without involving a policy
start-check.

<details>
<summary>C++</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/bindings/cpp/test/wiki/policy_api_test.cpp -->
```cpp
#include <openpit/openpit.hpp>

#include <string>
#include <string_view>

// BlockOnAdjustmentPolicy accepts the adjustment and reports an account block.
class BlockOnAdjustmentPolicy {
 public:
  [[nodiscard]] std::string_view Name() const noexcept {
    return "BlockOnAdjustmentPolicy";
  }

  [[nodiscard]] openpit::pretrade::PolicyAccountAdjustmentResult
  ApplyAccountAdjustment(
      const openpit::accountadjustment::Context& context,
      openpit::param::AccountId accountId,
      const openpit::accountadjustment::AccountAdjustment& adjustment,
      openpit::tx::Mutations& mutations,
      openpit::pretrade::AccountOutcomes& outcomes) const {
    static_cast<void>(accountId);
    static_cast<void>(adjustment);
    static_cast<void>(mutations);
    static_cast<void>(outcomes);
    static_cast<void>(context);
    openpit::pretrade::PolicyAccountAdjustmentResult result;
    result.accountBlocks.emplace_back(
        openpit::pretrade::RejectCode::AccountBlocked, std::string(Name()),
        "blocked by account-adjustment policy",
        "custom policy reported an account block from a callback");
    return result;
  }
};

// Builds the canonical single-leg order for `accountId`.
[[nodiscard]] openpit::model::Order AccountOrder(std::uint64_t accountId) {
  openpit::model::Order order;
  openpit::model::OrderOperation op;
  op.instrument = openpit::model::Instrument(::openpit::param::Asset("AAPL"),
                                             ::openpit::param::Asset("USD"));
  op.accountId = ::openpit::param::AccountId::FromUint64(accountId);
  op.side = openpit::model::Side::Buy;
  op.tradeAmount =
      openpit::model::TradeAmount::OfQuantity(
          openpit::param::Quantity::FromString("10"));
  op.price = openpit::param::Price::FromString("25");
  order.operation = std::move(op);
  return order;
}

openpit::EngineBuilder builder(openpit::SyncPolicy::None);
openpit::pretrade::CustomPolicy<BlockOnAdjustmentPolicy> policy(
    "BlockOnAdjustmentPolicy", BlockOnAdjustmentPolicy{});
builder.Add(policy);
openpit::Engine engine = builder.Build();

const openpit::param::AccountId accountId =
    openpit::param::AccountId::FromUint64(99224416);

// The accepted adjustment reports a block that the engine has already recorded.
openpit::accountadjustment::BalanceOperation balanceOp;
balanceOp.asset = ::openpit::param::Asset("USD");
openpit::accountadjustment::AccountAdjustment adjustment;
adjustment.operation =
    openpit::accountadjustment::Operation::OfBalance(std::move(balanceOp));
openpit::accountadjustment::Amount amount;
amount.balance = openpit::param::AdjustmentAmount::Absolute(
    openpit::param::PositionSize::FromString("0"));
adjustment.amount = std::move(amount);
const openpit::AdjustmentResult adjustmentResult = engine.ApplyAccountAdjustment(
    accountId,
    std::vector<openpit::accountadjustment::AccountAdjustment>{adjustment});
assert(adjustmentResult.Passed());
assert(adjustmentResult.accountBlocks.size() == 1);

// A later order on the same account is rejected with ACCOUNT_BLOCKED, without
// any start-check involvement.
openpit::pretrade::StartResult blocked =
    engine.StartPreTrade(AccountOrder(99224416));
```

</details>

<details>
<summary>Rust</summary>

<!-- Test mirror: https://github.com/openpitkit/pit/blob/main/crates/openpit/tests/examples_wiki.rs -->
```rust
struct BlockOnAdjustmentPolicy;

impl<Order, ExecutionReport, AccountAdjustment, Sync>
    PreTradePolicy<Order, ExecutionReport, AccountAdjustment, Sync>
    for BlockOnAdjustmentPolicy
where
    Sync: openpit::SyncMode,
{
    fn name(&self) -> &str {
        "BlockOnAdjustmentPolicy"
    }

    fn apply_account_adjustment(
        &self,
        _ctx: &AccountAdjustmentContext<
            <Sync as openpit::SyncMode>::StorageLockingPolicyFactory,
        >,
        _account_id: AccountId,
        _adjustment: &AccountAdjustment,
        _mutations: &mut Mutations,
    ) -> Result<PolicyAccountAdjustmentResult, Rejects> {
        Ok(PolicyAccountAdjustmentResult {
            account_adjustments: Vec::new(),
            account_blocks: vec![AccountBlock::new(
                "BlockOnAdjustmentPolicy",
                RejectCode::AccountBlocked,
                "blocked by account-adjustment policy",
                "custom policy reported an account block from a callback",
            )],
        })
    }
}
```

</details>

## Post-Trade Context

The post-trade hook `apply execution report` receives a **post-trade context**
as its first argument. It carries the realized outcome of an execution report
back into policy state and exposes a lazy account-group accessor for the
report's account.

- Go: `ApplyExecutionReport(ctx pretrade.PostTradeContext, report, adjustments, pnls) []reject.AccountBlock`; push account adjustments and account PnL into the two collectors.
- Python: `apply_execution_report(self, ctx: openpit.pretrade.PostTradeContext, report) -> PostTradeResult | None`; return `account_blocks`, `account_pnls`, and `account_adjustments` in that result.
- JavaScript: `applyExecutionReport(ctx: PostTradeContext, report) -> PostTradeResult | null | undefined`; return `accountBlocks`, `accountPnls`, and `accountAdjustments` in that result.
- C++: `std::vector<accounts::AccountBlock> ApplyExecutionReport(const pretrade::PostTradeContext&, const R&, pretrade::PostTradeAdjustments&, pretrade::PostTradePnls&) const`.
- Rust: `apply_execution_report(&self, ctx: &PostTradeContext<..>, report: &R) -> Option<PostTradeResult>`; return `account_blocks`, `account_pnls`, and `account_adjustments` in that result.

Unlike the pre-trade and account-adjustment contexts, the post-trade context
carries no `account control` handle. Go and C++ report post-trade account blocks
through the hook return and push account-adjustment and account-PnL outcomes
into their collectors. Python, JavaScript, and Rust return all three independent
channels as fields of `PostTradeResult`. The engine merges those per-policy
results into the result returned to the caller, as described in
[Policies](Policies.md).

### Reading the Account Group

The post-trade context exposes the report account's
[`account group id`](Account-Groups.md) through `account_group()` (Rust) /
`AccountGroup()` (Go, returning an `optional.Option`) / `account_group`
(Python). The pre-trade and account-adjustment contexts expose the same
accessor for their bound account. The lookup is performed once and cached for
the lifetime of the context, so a policy can branch on the account's group
cheaply:

- pre-trade context: the order's account; the accessor yields nothing when the
  order carries no account;
- account-adjustment context: the account being adjusted;
- post-trade context: the execution report's account.

This is the **account** group, distinct from the per-policy `policy group id`
used by [Pre-Trade Lock](Pre-Trade-Lock.md); see
[Account Groups](Account-Groups.md#account-groups-vs-policy-groups).

## Reading the Operation Account

The pre-trade context carries the operation's account itself, read from the
order once when the operation started. That value is what the engine admitted
the request on and what it blocks, so a policy needing the account takes it
from the context: `ctx.account_id()` (Rust), which yields the order accessor's
own error when that single read failed. Reading the order again is a second,
independent call into caller code and answers for no particular operation. See
[The Operation Account](Pre-trade-Pipeline.md#the-operation-account).

The Go, Python, C++, and JavaScript contexts do not expose the accessor yet, so
a custom policy in those languages reads the order. Its `account ID` accessor
must then return the same account for every read within one operation.

## Related Pages

- [Policies](Policies.md): built-in controls and policy catalog
- [Dynamic Policy Reconfiguration](Dynamic-Policy-Reconfiguration.md): retune
  built-in policies at runtime without rebuilding the engine
- [Pre-trade Pipeline](Pre-trade-Pipeline.md): request and reservation semantics
- [Pre-Trade Lock](Pre-Trade-Lock.md): emit `lock_prices` from a policy's
  `PolicyPreTradeResult` and reconcile them on execution reports
- [Account Adjustments](Account-Adjustments.md): batch rollback semantics
- [Account Blocking](Account-Blocking.md): the mutation finalizer contract, the
  engine-wide block, and the admin blocking API
- [Account Groups](Account-Groups.md): account-group registry and the lazy
  `account group` accessor on the policy contexts
- [Custom Go Types](Custom-Go-Types.md): Go ClientEngine and typed model composition
- [Custom Python Types](Custom-Python-Types.md): Python model subclasses and
  custom-policy callback typing
- [Custom JS Types](Custom-JS-Types.md): JavaScript/TypeScript custom order and
  execution-report fields
- [Custom Cpp Types](Custom-Cpp-Types.md): C++ typed model composition and adapters
- [Custom Rust Types](Custom-Rust-Types.md): Rust model composition patterns
