# Policy Authoring

> Conventions and patterns for creating policy authoring types in the Azure API Management policy toolkit. Use this skill when creating or modifying config records in src/Authoring/Configs/ or adding methods to section context interfaces.

- Skill: `azure/policy-authoring` (Agent Skill)
- Install (CLI): `npx skillmds@latest add azure/policy-authoring`
- Raw SKILL.md: https://api.skillmd.com/api/skills/azure/policy-authoring/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: Azure (https://skillmd.com/u/azure)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/azure/policy-authoring

---


# Policy Authoring Patterns

This skill describes how to create authoring types (config records and context interface methods) for policies in the Azure API Management policy toolkit.

## Config Record — `src/Authoring/Configs/{PolicyName}Config.cs`

### File Template

```csharp
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring;

/// <summary>
/// Configuration for the {xml-element-name} policy.<br/>
/// {Brief description of what the policy does.}
/// </summary>
public record {PolicyName}Config
{
    /// <summary>
    /// {Property description}. Policy expressions are allowed.
    /// </summary>
    [ExpressionAllowed]
    public required {Type} {PropertyName} { get; init; }

    /// <summary>
    /// Optional. {Property description}.
    /// </summary>
    public {Type}? {OptionalPropertyName} { get; init; }
}
```

### Related Attributes

- **`[Document]`** (`src/Authoring/Attributes/DocumentAttribute.cs`) — Marks a class as a policy document. Optional `name` parameter; `Scope` and `Type` properties control document scope and type.
- **`[Expression]`** (`src/Authoring/Attributes/ExpressionAttribute.cs`) — Marks a method as a policy expression (captures source file path via `[CallerFilePath]`). Used by the compiler to identify expression helper methods.
- **`[ExpressionAllowed]`** (`src/Authoring/Attributes/ExpressionAllowedAttribute.cs`) — Marks config properties or parameters that accept policy expressions.

### Property Rules

- **Required properties**: use the `required` keyword + `init` setter.
- **Optional properties**: use a nullable type (`?`) + `init` setter. Do **not** use `required`.
- **Expression-enabled properties**: decorate with `[ExpressionAllowed]` attribute. Add "Policy expressions are allowed." to the XML doc.
- **Constrained properties** (enums, allowed values): use either:
  - A C# `enum` type (preferred for fixed, type-safe values)
  - A `string` property with XML doc listing allowed values (for values documented outside code)
  - Validation is applied at **compilation time** by the compiler class, **not at authoring time**. The config record accepts any value; the compiler reports diagnostics if invalid.
- **Sub-configs** (child element arrays): define as separate `public record` types in the same file.
- **Sub-config naming convention**: Use descriptive nouns that reflect their purpose (e.g., `ApiRateLimit`, `AddressRange`, `MatchCondition`), not generic names like `Item` or `Element`.
- **Every public type and member** must have `/// <summary>` XML documentation covering:
  - Purpose
  - Required vs optional status
  - Default value (if any)
  - Expression support (yes/no)
  - Allowed values / enums (if applicable)

### Real Examples

**Simple attributes with expression support** — `RateLimitByKeyConfig.cs`:
```csharp
public record RateLimitByKeyConfig
{
    [ExpressionAllowed]
    public required int Calls { get; init; }

    [ExpressionAllowed]
    public required int RenewalPeriod { get; init; }

    [ExpressionAllowed]
    public required string CounterKey { get; init; }

    [ExpressionAllowed]
    public bool? IncrementCondition { get; init; }

    public string? RetryAfterHeaderName { get; init; }
}
```

**Nested sub-records** — `RateLimitConfig.cs`:
```csharp
public record RateLimitConfig
{
    public required int Calls { get; init; }
    public required int RenewalPeriod { get; init; }
    public string? RetryAfterHeaderName { get; init; }
    public ApiRateLimit[]? Apis { get; init; }
}

public record ApiRateLimit : EntityLimitConfig
{
    public OperationRateLimit[]? Operations { get; init; }
}

public abstract record EntityLimitConfig
{
    public string? Name { get; init; }
    public string? Id { get; init; }
    public required int Calls { get; init; }
    public required int RenewalPeriod { get; init; }
}
```

**Typed child arrays** — `IpFilterConfig.cs`:
```csharp
public record IpFilterConfig
{
    /// <summary>
    /// The action to apply: 'allow' or 'deny'. Policy expressions are allowed.
    /// </summary>
    [ExpressionAllowed]
    public required string Action { get; init; }

    /// <summary>
    /// Optional. IP addresses. Policy expressions are allowed.
    /// </summary>
    [ExpressionAllowed]
    public string[]? Addresses { get; init; }

    /// <summary>
    /// Optional. IP address ranges.
    /// </summary>
    public AddressRange[]? AddressRanges { get; init; }
}

/// <summary>
/// An IP address range with 'from' and 'to' octets.
/// </summary>
public record AddressRange
{
    /// <summary>
    /// The starting IP address. Policy expressions are allowed.
    /// </summary>
    [ExpressionAllowed]
    public required string From { get; init; }

    /// <summary>
    /// The ending IP address. Policy expressions are allowed.
    /// </summary>
    [ExpressionAllowed]
    public required string To { get; init; }
}
```

**Constrained values with enum** — `AdvancedPolicyConfig.cs` (example pattern):
```csharp
/// <summary>
/// The caching behavior for the policy action.
/// </summary>
public enum CachingBehavior
{
    /// <summary>Cache the response.</summary>
    Store,
    /// <summary>Reuse a cached response if available.</summary>
    Validate,
    /// <summary>Bypass cache entirely.</summary>
    Bypass
}

public record AdvancedPolicyConfig
{
    /// <summary>
    /// Required. The caching behavior: 'Store', 'Validate', or 'Bypass'.
    /// </summary>
    public required CachingBehavior Behavior { get; init; }
}
```

## Context Interface Methods

Add method signatures to the appropriate section interfaces. Policies may appear in one or more sections:

- `src/Authoring/IInboundContext.cs`
- `src/Authoring/IOutboundContext.cs`
- `src/Authoring/IBackendContext.cs`
- `src/Authoring/IOnErrorContext.cs`
- `src/Authoring/IFragmentContext.cs`

### Method Signature Template

```csharp
/// <summary>
/// {Description of the policy behavior}.<br/>
/// Compiled to <a href="{documentation-url}">{xml-element-name}</a> policy.
/// </summary>
/// <param name="config">
/// Configuration for the {xml-element-name} policy.
/// </param>
void {MethodName}({PolicyName}Config config);
```

### Rules

- The method name is PascalCase and matches the config name minus the `Config` suffix (e.g., `RateLimit` for `RateLimitConfig`).
- All section interfaces extend `IHaveExpressionContext`, giving access to `ExpressionContext`.
- For parameters that are expressions (not config objects), use `[ExpressionAllowed]` on the parameter itself.
- Some policies use direct parameters instead of a config object (e.g., `void SetMethod(string method)`). Use this pattern only for policies with 1–2 simple parameters and no optional fields.

### `IFragmentContext` Duplication

`IFragmentContext` currently duplicates method signatures from the section-specific interfaces (see the `//TODO` comment at the top of the file). **Every time you add a policy method to any section interface, you must also add the same signature to `IFragmentContext.cs`.** Copy the exact same method signature verbatim. Do not attempt to refactor this pattern.

