# POLICY.md

A POLICY.md is the complete rule set for one scenario: an allocation band for each asset class,
and optional position size ranges for each one, one for long holdings and one for shorts. Nothing
else. Allocait reads the file, shows you exactly what would change, and only applies it once you
say so.

## Example

POLICY.md format:

```markdown
---
version: 1
name: Core
classes:
  Equities: { target: <target>, min: <min>, max: <max>, position_size: { min: <min>, max: <max> }, short_position_size: { min: <min>, max: <max> } }
  Fixed Income: { target: <target>, min: <min>, max: <max> }
  Cash: { min: <min>, max: <max> }
---

# Anything you like

Prose for you and your agent. Allocait ignores everything below the frontmatter.
```

Rules:
- Replace every <...> with a number the user gave you. If they gave none, leave the class out. A target, position_size or short_position_size they gave no number for is left off that class's line instead.
- The file starts with a --- line, YAML rules, then another --- line. Nothing goes before the first ---.
- Use spaces to indent, never tabs. A tab in the file is read as an error, not silently fixed.
- Each class needs a min, a max, or both: that pair is its range, how far its share of the portfolio can move before you would act. A class can instead hold only a position_size or short_position_size, with no min or max: the class total is then not checked, only each holding. A class with only a target is not allowed; Allocait never picks a range for you, and a target needs a min or max. A max of zero means none of the class.
- target, when you give one, must sit between min and max.
- position_size is optional. It is the range one single holding in that class sits in, as a percent of the whole portfolio. { min: <min>, max: <max> } means each position is between those two percents of the portfolio. A max is required; leave min out for a cap with no floor, e.g. { max: <max> } means no single holding over that percent.
- short_position_size is optional and has the same shape as position_size. It is the range one short holding in that class sits in, as a percent of the whole portfolio, counted without the minus sign. A max is required; leave min out for a cap with no floor. A short in a class with no short_position_size is not checked.
- Percent values can be a plain number or a string with a percent sign after the number.
- Use these class names when they fit: Cash, Equities, Fixed Income, Currencies, Crypto, Commodities, Options. Any other name becomes a new custom class.
- If a document splits one of these into parts (domestic and international equity, for example), ask whether to keep them as separate classes, named as written, or combine them into the matching class above. If combining, add the targets, the minimums, and the maximums together.
- If a document uses a name close to one of these but not the same (Global Equities or Investment Grade Fixed Income, for example), ask whether to keep the name as written or use the matching name above. A name that is not an exact match becomes a new class of its own.
- A range written as a value plus or minus a spread becomes a min of the value minus the spread and a max of the value plus the spread.
- Anything below the second --- line is free text. Allocait ignores it, but it is a good place for notes or a review date.

## Every key

- `version`: always 1. Missing is read as version 1; anything else is an error.
- `name`: optional, a label for the scenario. Import never renames anything because of it.
- `classes`: required, at least one entry. Each key is an asset class name; each value is a
  rule.

A rule can hold:
- `min`, `max`: the allocation band, as a percent of the portfolio. Give at least one; the
  other defaults to 0 or 100. A class with a `position_size` or `short_position_size` can leave
  both out: it then has no band, and the class total is not checked. Allocait judges a class
  with nothing in it at 0%, so a `min`
  above 0 on a class the user holds nothing in is a breach until the class reaches it (ADR-069).
- `target`: optional, must sit between min and max.
- `position_size`: optional `{ min, max }`, the range, as a percent of the portfolio, that one
  holding in this class sits in. `max` is required; `min` defaults to 0 when left out, so
  `{ max: <max> }` means no single holding over that percent, with no floor. Leave the whole key
  out and any size range already set for that class is cleared.
- `short_position_size`: optional `{ min, max }`, the same shape as `position_size`, for a short
  holding (negative shares) in this class. A short is judged by its size without the minus sign, as
  a percent of the portfolio. `max` is required; `min` defaults to 0. A short in a class with no
  `short_position_size` is not checked. Leave the key out and any short size range already set
  for that class is cleared.

A name that matches a known alias becomes the class it stands for when you import: Stocks and
Equity match Equities; Bonds matches Fixed Income; Forex and FX match Currencies. ETF, ETFs, Fund
and Funds match Equities too: Allocait files each fund under what it holds, so those two classes
are gone. Your own class of that name comes first (your own Equities, say), then the system class.
A class you own that is named exactly like the alias always wins over the alias.

## The rules

- A class needs a min, a max, or both, or a position_size or short_position_size. Allocait never picks a range for you.
- min must be less than max. A max of zero is the one exception: it means none of the class.
- target, when given, must sit between min and max, so a class with a target needs a min or max.
- position_size needs a max; min defaults to 0 when left out, and min must not be greater than max.
- short_position_size follows the same rules and stands apart from position_size: either, both or neither.
- Class names cannot repeat.
- An unknown key in a rule is an error, not a silent skip, so a typo does not vanish quietly.

## What Allocait ignores

Any key at the top level other than `version`, `name`, and `classes`, and everything below
the second --- line: inputs, monitors, playbooks, levels, a rebalance schedule, review cadence,
holdings, prices. Keep any of that in the free text if you want it on hand for you or your
agent.

## Importing

Import replaces the scenario's whole rule set with what is in the file: classes in the file get
rules, and any class not in the file loses its rule. Import never deletes, renames, or reorders
an asset class; a name that matches nothing becomes a new custom class.

Before anything is written, Allocait shows a preview: every class that would change, be added,
or be removed, the old value next to the new one. Nothing is applied until you approve it.

Every import saves a restore point first, holding the scenario's prior rules, so you can undo it
back to exactly what was there before.

## No rules yet

A scenario with no rules yet has no POLICY.md to show. Allocait says so plainly instead of
showing an empty file, until you set a rule on the Rules tab or import one here.

## For developers

Allocait's agent-facing surface for policies:

**agent-api** (`x-api-key` header; base `https://api.allocait.app/functions/v1/agent-api`,
see `docs/openapi.yaml`):
- `GET /scenarios/:id/policy` -- the scenario's current policy as POLICY.md text, plus its parsed
  rules. A scenario with no rules yet returns `policy: null` and an empty `rules` list, instead
  of an empty file.
- `POST /scenarios/:id/policy` -- body `{ policy, dry_run? }`. The policy text must be at most
  102,400 bytes (100 KiB), checked before parsing, and describe at most 500 classes,
  checked right after parsing. Parses the text first: a parse error returns `400` with
  the issues and calls nothing. Otherwise previews (`dry_run: true`, no write) or applies the
  file, and returns the diff plus a `restore_point_id`. Each changed entry also carries
  `written_as`: the name exactly as written in the file, when it differs from the class the name
  resolved to, else `null`.
- `POST /scenarios/:id/position-rules` -- body `{ asset_class, band_min_pct?, band_max_pct?,
  target_pct?, size_min_pct?, size_max_pct?, short_size_min_pct?, short_size_max_pct?,
  create_class? }`. Sets one class's rule and keeps every other rule; both band fields `null`
  removes it. Saves a restore point first and returns `before`, `after` and its
  `restore_point_id`.
- `GET /policy/restore-points?scenario_id=` -- the scenario's restore points, newest first.
- `POST /policy/restore-points/:id/restore` -- restore a scenario to a saved restore point.
- `GET /policy/spec` -- this spec, plus the examples and prompts below, so an agent can learn the
  format from the API itself.

**MCP** (server `allocait`):
- Tools: `get_policy` (a scenario's rules as POLICY.md text, every asset class with its id and
  aliases, and the latest 5 restore points), `preview_policy` (text in, a diff back, never
  writes), `apply_policy` (text in, writes, returns a restore point id), `save_class_rule` (one
  class's band, target, size range or short size range, keeping every other rule; returns a
  restore point id),
  `restore_policy` (restore point id in, writes). `preview_policy` and `apply_policy` enforce the
  same caps as agent-api: size before parsing, class count right after.
- Resources: `allocait://policy/spec`, `allocait://scenarios/{scenario_id}/policy` (the current
  POLICY.md text, or a short "No rules yet." note when the scenario has none),
  `allocait://policy/restore-points`.
- Prompt: `draft_policy` (optional `notes` argument), built from the "Help me write my policy"
  and "Convert my advisor's investment policy statement" prompts below.

**CLI** (inside the Allocait repo, no network needed):
```
deno run --allow-read --allow-env=LOG_TOKENS,LOG_STREAM scripts/policy-check.ts POLICY.md
deno run --allow-read --allow-env=LOG_TOKENS,LOG_STREAM scripts/policy-check.ts - < POLICY.md
deno run --allow-read --allow-env=LOG_TOKENS,LOG_STREAM scripts/policy-check.ts POLICY.md --json
deno run --allow-read --allow-env=LOG_TOKENS,LOG_STREAM scripts/policy-check.ts POLICY.md --format
npm run policy:check -- POLICY.md
```
The `--allow-env` names are npm:yaml@2's own debug-logging switches, read
unconditionally by its parser; the CLI never reads the environment itself.
Exits `1` when the file has any error, `0` otherwise.
