> For the complete documentation index, see [llms.txt](https://govplane.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://govplane.gitbook.io/docs/documentation/schemas/policy-draft.md).

# Policy Draft

A Policy Draft is the authoring format. A draft is what you edit; `govplane build` compiles it into a policy bundle.

A draft is a runtime policy set **without** the bundle's scope and integrity metadata — no `orgId`, `projectId`, `bundleVersion`, `checksum` or `signature`. `build` adds those.

### Two shapes

The toolkit accepts either, and normalises to build-ready on load.

| Shape           | Keyed by     | Written by                        |
| --------------- | ------------ | --------------------------------- |
| **Build-ready** | `policies[]` | `govplane policies`, hand editing |
| **Analyze**     | `drafts[]`   | `govplane analyze`                |

A file with neither is rejected:

```
The draft file must contain a "policies" or "drafts" array.
```

***

### Build-ready draft

What `build` consumes.

| Field           | Type                 | Required | Notes                                  |
| --------------- | -------------------- | -------- | -------------------------------------- |
| `schemaVersion` | `string` \| `number` | ✅        | `"1.0"` as written by the toolkit      |
| `policies`      | `DraftPolicy[]`      | ✅        | May be empty                           |
| `generatedAt`   | `string`             | —        | ISO-8601                               |
| `env`           | `string`             | —        | `prod` \| `staging` \| `dev` \| `test` |

```json
{
  "schemaVersion": "1.0",
  "generatedAt": "2026-08-08T09:00:00.000Z",
  "env": "prod",
  "policies": [
    {
      "policyKey": "login-protection",
      "activeVersion": 1,
      "defaults": { "effect": "allow" },
      "rules": [
        {
          "id": "deny-after-five-failures",
          "status": "active",
          "priority": 100,
          "target": { "service": "auth", "resource": "login", "action": "authenticate" },
          "when": { "op": "gte", "path": "ctx.failedAttempts", "value": 5 },
          "effect": { "type": "deny" }
        }
      ]
    }
  ]
}
```

{% hint style="info" %}
`schemaVersion` is the string `"1.0"` in a draft, but the integer `1` in a bundle. They are different documents with different version lines.
{% endhint %}

#### Policy

| Field              | Type            | Required | Notes                                                    |
| ------------------ | --------------- | -------- | -------------------------------------------------------- |
| `policyKey`        | `string`        | ✅        | Unique across the draft                                  |
| `rules`            | `DraftRule[]`   | ✅        | May be empty                                             |
| `activeVersion`    | `number`        | —        | Must be a number if present                              |
| `defaults`         | `PolicyDefault` | —        |                                                          |
| `discoveredTarget` | `Target`        | —        | Advisory, written by `analyze`. Not carried into bundles |

#### Rule

Identical to a runtime rule.

| Field                       | Type                       | Required | Notes                                   |
| --------------------------- | -------------------------- | -------- | --------------------------------------- |
| `id`                        | `string`                   | ✅        | Unique within the policy                |
| `priority`                  | `number`                   | ✅        |                                         |
| `target`                    | `Target`                   | ✅        | All three parts non-empty               |
| `effect`                    | `Effect`                   | ✅        |                                         |
| `status`                    | `"active"` \| `"disabled"` | —        | Validated if present; `build` writes it |
| `when`                      | `WhenAst`                  | —        |                                         |
| `thenEffect` / `elseEffect` | `Effect`                   | —        |                                         |

Effects, conditions and defaults use exactly the vocabulary in the bundle schema.

***

### Analyze draft

What `govplane analyze` writes: discovered evaluation points, not yet decisions.

| Field           | Type                  | Notes    |
| --------------- | --------------------- | -------- |
| `schemaVersion` | `string`              | `"1.0"`  |
| `generatedAt`   | `string`              | ISO-8601 |
| `drafts`        | `AnalyzeDraftEntry[]` |          |

#### Entry

| Field                                                           | Type                            | Notes                                                                    |
| --------------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------ |
| `id`                                                            | `string`                        |                                                                          |
| `status`                                                        | `string`                        |                                                                          |
| `confidence`                                                    | `string`                        | `high` \| `medium` \| `low` — how much of the target resolved statically |
| `target`                                                        | `{ service, resource, action }` | Discovered from the source                                               |
| `availableContext`                                              | `{ key, source?, type? }[]`     | Context keys seen at the call site                                       |
| `suggestedPolicy`                                               | see below                       |                                                                          |
| `sources`                                                       | `{ file, line, column }[]`      | Where the call was found                                                 |
| `serviceExpression` / `resourceExpression` / `actionExpression` | —                               | Preserved when a target part was dynamic                                 |
| `matchedPolicies`                                               | `string[]`                      | Existing policies already covering this target                           |

`suggestedPolicy` is a shell: `{ policyKey, friendlyName, target, rules: [] }`. **`rules` is always empty** — `analyze` finds where policy is evaluated; it never invents the decision.

```json
{
  "schemaVersion": "1.0",
  "generatedAt": "2026-08-08T09:00:00.000Z",
  "drafts": [
    {
      "id": "auth-login-authenticate",
      "status": "uncovered",
      "confidence": "high",
      "target": { "service": "auth", "resource": "login", "action": "authenticate" },
      "availableContext": [{ "key": "failedAttempts", "source": "ctx.failedAttempts", "type": "number" }],
      "suggestedPolicy": {
        "policyKey": "auth-login-authenticate",
        "friendlyName": "Auth · login · authenticate",
        "target": { "service": "auth", "resource": "login", "action": "authenticate" },
        "rules": []
      },
      "sources": [{ "file": "src/routes/login.ts", "line": 42, "column": 18 }]
    }
  ]
}
```

#### Normalisation

Loading an analyze document converts each entry to a build-ready policy:

| Analyze                                                              | Build-ready                   |
| -------------------------------------------------------------------- | ----------------------------- |
| `suggestedPolicy.policyKey`, else `id`, else `discovered-policy-<n>` | `policyKey`                   |
| `suggestedPolicy.activeVersion`, else `1`                            | `activeVersion`               |
| `suggestedPolicy.rules`                                              | `rules`                       |
| `suggestedPolicy.defaults`                                           | `defaults` (when present)     |
| `target`                                                             | `discoveredTarget` (advisory) |

The discovered target is kept rather than dropped — it is the one thing `analyze` learned from the codebase, and it saves finding it again when writing the first rule. `build` reads targets from rules, so it stays advisory.

***

### Draft vs bundle

|                          | Draft            | Bundle                           |
| ------------------------ | ---------------- | -------------------------------- |
| `schemaVersion`          | `"1.0"` (string) | `1` (integer)                    |
| `orgId` / `projectId`    | ✗                | Only required for Govplane Cloud |
| `bundleVersion`          | ✗                | ✓                                |
| `checksum` / `signature` | ✗                | ✓                                |
| `env`                    | optional         | required                         |
| Empty `rules`            | allowed, warned  | allowed                          |
| Purpose                  | authoring        | evaluation                       |

### Validation codes

From `govplane validate` on a draft.

| Code                                                                                            | Meaning                                            |
| ----------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| `DOCUMENT_NOT_OBJECT`                                                                           | Not a JSON object                                  |
| `INVALID_DRAFT_SCHEMA`                                                                          | `schemaVersion` missing, or not a string or number |
| `INVALID_ENV`                                                                                   | Not one of the four environments                   |
| `MISSING_POLICY_KEY` / `DUPLICATE_POLICY_KEY`                                                   |                                                    |
| `INVALID_ACTIVE_VERSION`                                                                        | Not a number                                       |
| `RULES_NOT_ARRAY`                                                                               |                                                    |
| `MISSING_RULE_ID` / `DUPLICATE_RULE_ID`                                                         | Duplicate is within one policy                     |
| `INVALID_RULE_STATUS` / `INVALID_RULE_PRIORITY` / `INVALID_RULE_TARGET` / `INVALID_RULE_EFFECT` |                                                    |
| `INVALID_DRAFT_ENTRY` / `MISSING_DRAFT_TARGET`                                                  | Analyze shape only                                 |

Warnings do not block a build:

| Code                      | Meaning                             |
| ------------------------- | ----------------------------------- |
| `EMPTY_DOCUMENT`          | No policies or drafts               |
| `INCOMPLETE_DRAFT`        | An analyze entry still has no rules |
| `DYNAMIC_WILDCARD_TARGET` | A discovered target resolved to `*` |

### Commands

```bash
govplane analyze --source .              # discover call sites → drafts[]
govplane policies add-policy --policy-key login-protection --defaults-effect allow
govplane validate                        # check before building
govplane build --signed                  # drafts → bundle
```

### See also

* [Policy Bundle Schema](/docs/documentation/schemas/policy-bundle.md) — the compiled output
* [Policies and Rules ](/docs/documentation/basic-concepts/policies-and-rules.md)
* [Conditional Rules](/docs/documentation/basic-concepts/conditional-rules.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://govplane.gitbook.io/docs/documentation/schemas/policy-draft.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
