# RFC 0001: Assay Policy DSL v1.1

**Status:** Draft
**Author:** Evals Lead
**Date:** 2025-12-30
**Target Release:** v1.1.0

---

## Abstract

This RFC defines the Assay Policy DSL v1.1, a declarative YAML-based language for specifying deterministic constraints on AI agent tool call sequences. The DSL enables policy enforcement without LLM-based evaluation, supporting both static access control and temporal sequence constraints.

---

## 1. Design Principles

### 1.1 Core Tenets

| Principle | Rationale |
|-----------|-----------|
| **Deterministic** | No LLM calls, no probabilistic evaluation. Same trace + policy = same verdict. |
| **Declarative** | Describe *what* constraints exist, not *how* to check them. |
| **Composable** | Rules combine predictably. No implicit interactions. |
| **Rego-mappable** | Static constraints should export to OPA/Rego for enterprise policy unification. |
| **Fail-explicit** | Every failure produces actionable diagnostics: rule ID, event index, context. |

### 1.2 Non-Goals

- **Not a workflow engine**: We validate traces, not orchestrate execution
- **Not semantic analysis**: No intent detection, no content classification
- **Not a full policy language**: Deliberately limited expressivity to ensure determinism

### 1.3 Relationship to Existing Standards

| Standard | Relationship |
|----------|--------------|
| **OPA/Rego** | Static constraints (`allow`, `deny`, `require_args`) can export to Rego. Temporal constraints are Assay-specific. |
| **MCP** | Assay validates MCP tool calls. MCP Authorization handles identity; Assay handles behavioral policy. |
| **OpenTelemetry** | Traces follow OTel GenAI semantic conventions. Assay policies reference tool names from spans. |

---

## 2. Document Structure

```yaml
# Every Assay policy document
version: "1.1"                    # Required: DSL version
name: "policy-name"               # Required: Unique identifier
description: "..."                # Optional: Human-readable description

# Optional metadata
metadata:
  author: "team-name"
  created: "2025-12-30"
  tags: ["customer-service", "tier-1"]

# Policy sections (all optional, but document must have at least one)
tools: { ... }                    # Static tool constraints
sequences: [ ... ]                # Temporal sequence constraints
aliases: { ... }                  # Tool name mappings
on_error: allow | deny            # Fail-safe behavior (default: deny)
```

---

## 3. Static Constraints (`tools`)

Static constraints evaluate each tool call independently. They are stateless and map directly to OPA/Rego policies.

### 3.1 Allowlist

```yaml
tools:
  allow:
    - SearchKnowledgeBase
    - GetCustomerInfo
    - CreateTicket
```

**Semantics:**
- If `allow` is present, ONLY listed tools are permitted
- Unlisted tools are DENIED
- Empty `allow: []` denies all tools

**Rego equivalent:**
```rego
allow { input.tool in {"SearchKnowledgeBase", "GetCustomerInfo", "CreateTicket"} }
```

### 3.2 Denylist

```yaml
tools:
  deny:
    - DeleteAccount
    - DropDatabase
    - AdminEscalate
```

**Semantics:**
- Listed tools are ALWAYS denied, regardless of allowlist
- `deny` takes precedence over `allow`
- Empty `deny: []` has no effect

**Rego equivalent:**
```rego
deny { input.tool in {"DeleteAccount", "DropDatabase", "AdminEscalate"} }
```

### 3.3 Combining Allow and Deny

```yaml
tools:
  allow:
    - SearchKnowledgeBase
    - GetCustomerInfo
    - CreateTicket
    - AdminEscalate      # Listed in allow...
  deny:
    - AdminEscalate      # ...but denied here. Deny wins.
```

**Evaluation order:**
1. Check `deny` list → if match, DENY
2. Check `allow` list → if match, ALLOW
3. If `allow` exists but no match → DENY
4. If no `allow` exists → ALLOW (implicit allow-all)

### 3.4 Required Arguments

```yaml
tools:
  require_args:
    CreateTicket:
      - customer_id
      - description
    SendEmail:
      - recipient
      - subject
```

**Semantics:**
- Tool call DENIED if any required argument is missing
- Argument presence check only (not value validation)
- Missing tool in `require_args` has no argument requirements

**Rego equivalent:**
```rego
deny {
    input.tool == "CreateTicket"
    not input.args.customer_id
}
```

### 3.5 Argument Value Constraints

```yaml
tools:
  arg_constraints:
    TransferMoney:
      amount:
        max: 10000
        min: 1
      currency:
        enum: ["USD", "EUR", "GBP"]

    SetDiscount:
      percentage:
        max: 50
        pattern: "^[0-9]+$"
```

**Supported constraint types:**
- `min` (number): Minimum value (inclusive)
- `max` (number): Maximum value (inclusive)
- `enum` (array): Value must be in list
- `pattern` (string): Regex pattern (Rust `regex` crate syntax)
- `required` (boolean): Argument must be present

---

## 4. Temporal Constraints (`sequences`)

Temporal constraints track state across the tool call sequence. These are Assay-specific.

### 4.1 `eventually` - Must Occur Within N Calls

```yaml
sequences:
  - id: search-before-action
    type: eventually
    tool: SearchKnowledgeBase
    within: 3
```

**Semantics:**
- Tool MUST be called within the first `within` calls
- If not called by event index `within - 1`, trace FAILS

### 4.2 `max_calls` - Rate Limiting

```yaml
sequences:
  - id: limit-api-calls
    type: max_calls
    tool: ExternalAPICall
    max: 3
```

**Semantics:**
- Tool may be called at most `max` times in the trace
- Call `max + 1` is DENIED
- Counter resets per trace

### 4.3 `before` - Ordering Constraint

```yaml
sequences:
  - id: authenticate-first
    type: before
    first: Authenticate
    then: AccessSecureData
```

**Semantics:**
- `then` tool DENIED until `first` tool has been called
- Once `first` is called, `then` is allowed for rest of trace

### 4.4 `after` - Post-Condition

```yaml
sequences:
  - id: log-after-mutation
    type: after
    trigger: CreateRecord
    then: AuditLog
    within: 2
```

**Semantics:**
- After `trigger` is called, `then` MUST be called within `within` calls

### 4.5 `never_after` - Forbidden Sequence

```yaml
sequences:
  - id: no-delete-after-archive
    type: never_after
    trigger: ArchiveRecord
    forbidden: DeleteRecord
```

**Semantics:**
- Once `trigger` is called, `forbidden` is DENIED for rest of trace

### 4.6 `sequence` - Exact Ordering

```yaml
sequences:
  - id: standard-flow
    type: sequence
    tools: [Search, Analyze, Create]
    strict: false
```

**Semantics:**
- `strict: false`: Other tools allowed between sequence members
- `strict: true`: No other tools allowed between

---

## 5. Tool Aliases

Aliases provide abstraction over tool name variations.

```yaml
aliases:
  Search:
    - SearchKnowledgeBase
    - SearchWeb
```

**Semantics:**
- Alias matches any member tool
- Aliases are NOT recursive

---

## 6. Fail-Safe Behavior

```yaml
on_error: allow | deny    # Default: deny
```

**Semantics:**
- `deny`: Policy evaluation errors → tool call DENIED
- `allow`: Policy evaluation errors → tool call ALLOWED
