# Invariants

Invariants are natural-language constraints on plan execution. They flow into the
decision tree during Strategize and are reconciled by the Invariant Reconciliation
Actor before any strategy work begins.

## Scope Hierarchy

| Scope     | Description                                              |
|-----------|----------------------------------------------------------|
| `GLOBAL`  | Applies to every plan in the system                      |
| `PROJECT` | Applies to plans targeting a specific project            |
| `ACTION`  | Defined in an action template; promoted on `plan use`    |
| `PLAN`    | Attached directly to a specific plan                     |

## Merge Precedence

When computing the effective set of invariants for a plan, the precedence chain is:

```
plan > project > global
```

- **Plan-level** invariants take highest precedence.
- **Project-level** invariants apply next.
- **Global-level** invariants are lowest precedence.
- **Action-level** invariants are promoted to plan scope when `plan use` is called.

### De-duplication

Invariants are de-duplicated by text (case-insensitive). When the same constraint
text appears at multiple scopes, only the highest-precedence copy is kept.

### Merge Example

Given:

- Global: `"Never delete production data"`, `"Log all changes"`
- Project (`myapp`): `"All API changes need tests"`, `"Log all changes"`
- Plan: `"Use Python 3.13 only"`

The effective set for a plan targeting `myapp` would be:

1. `"Use Python 3.13 only"` (plan)
2. `"All API changes need tests"` (project)
3. `"Log all changes"` (project — shadows the global duplicate)
4. `"Never delete production data"` (global)

## Enforcement

At the start of the Strategize phase, the Invariant Reconciliation Actor:

1. Collects the effective invariant set for the plan.
2. Evaluates each invariant against the current plan context.
3. Creates an `InvariantEnforcementRecord` for each invariant.
4. Records an `invariant_enforced` decision in the decision tree.

### Enforcement Record

Each record contains:

| Field            | Description                                      |
|------------------|--------------------------------------------------|
| `invariant_id`   | ULID of the invariant                            |
| `enforced`       | Whether the invariant was successfully enforced  |
| `actor_response` | Response text from the reconciliation actor      |
| `decision_id`    | ULID of the associated decision node             |

## Violation Model

When an invariant is violated during execution, an `InvariantViolation` is created:

| Field           | Description                                       |
|-----------------|---------------------------------------------------|
| `invariant_id`  | ULID of the violated invariant                    |
| `violated_text` | The invariant text that was violated              |
| `severity`      | `error`, `warning`, or `info`                     |
| `details`       | Additional violation context                      |

## CLI Usage

### Add an invariant

```bash
# Global invariant
agents invariant add --global "Never delete production data"

# Project-scoped invariant
agents invariant add --project myapp "All API changes need tests"

# Plan-specific invariant
agents invariant add --plan 01HXYZ... "Use Python 3.13 only"

# Action-scoped invariant
agents invariant add --action local/code-coverage "Minimum 80% coverage"
```

### List invariants

```bash
# List all active invariants
agents invariant list

# Filter by scope
agents invariant list --global
agents invariant list --project myapp

# Show merged effective set for a project
agents invariant list --effective --project myapp

# Filter by regex
agents invariant list "data.*safe"

# JSON output
agents invariant list --format json
```

### Remove an invariant

```bash
# With confirmation prompt
agents invariant remove 01HXYZ...

# Skip confirmation
agents invariant remove --yes 01HXYZ...
```

## Relationship with PlanInvariant

The existing `PlanInvariant` model in `plan.py` represents invariants already
attached to a plan with an `InvariantSource` provenance tag. The new `Invariant`
model in `invariant.py` is the standalone storage model used by `InvariantService`
for cross-scope management. When a plan is created via `plan use`, action
invariants are converted to `PlanInvariant` entries on the plan itself.
