> 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/cli/practical-example-from-start-to-end.md).

# Practical Example from Start to End

From an application with a hard-coded `if` to a signed bundle your service evaluates. Every command and every output on this page is real.

**The goal:** lock an account after five failed login attempts, without that number living in the code.

{% stepper %}
{% step %}

## Setup

```bash
npm install --global @govplane/cli @govplane/cli-toolkit
govplane activate
```

The application already calls Govplane, but no policy covers it yet:

```ts
// src/login.ts
export async function login(email: string, failedAttempts: number) {
  const decision = govplane.evaluate({
    target: { service: 'auth', resource: 'login', action: 'authenticate' },
    context: { failedAttempts },
  });
  if (decision.decision === 'deny') throw new Error('locked');
  return authenticate(email);
}
```

```bash
mkdir -p governance
```

{% hint style="info" %}
Do **not** run `working-folder init` yet. It creates an empty `policy-drafts.json`, and `analyze` refuses to overwrite an existing draft. Run `analyze` first and let it create the draft.
{% endhint %}
{% endstep %}

{% step %}

## Find the evaluation points

```bash
govplane analyze --source ./src -w ./governance
```

```
Govplane Analysis

Source:
  src
  1 file scanned

Discovered 1 policy:

  missing  auth-login-authenticate
     auth / login / authenticate
     login.ts:2

Draft:
  governance/policy-drafts.json

Result:
  Analysis completed successfully

Drafts carry no rules — analyze never invents them.
Add rules with "govplane policies add-rule", then run "govplane build".
```

The draft it wrote is a shell, deliberately:

```json
{
  "schemaVersion": "1.0",
  "generatedAt": "2026-08-08T15:24:11.219Z",
  "policies": [
    {
      "policyKey": "auth-login-authenticate",
      "activeVersion": 1,
      "friendlyName": "Auth Login Authenticate",
      "discoveredTarget": { "service": "auth", "resource": "login", "action": "authenticate" },
      "rules": []
    }
  ]
}
```

`analyze` learned the target from your code. What the answer should be is the one thing it will not guess.
{% endstep %}

{% step %}

## Decide the default

An unknown login should succeed; only the failure case is special.

```bash
govplane policies update-policy -w ./governance \
  --policy-key auth-login-authenticate \
  --defaults-effect allow
```

```
Policy updated: auth-login-authenticate

Active version: 1
Defaults effect: allow
```

{% endstep %}

{% step %}

## Add the rule

```json
// deny-retries.json
{
  "id": "deny-after-five-failures",
  "status": "active",
  "priority": 100,
  "target": { "service": "auth", "resource": "login", "action": "authenticate" },
  "when": { "op": "gte", "path": "failedAttempts", "value": 5 },
  "effect": { "type": "deny" }
}
```

```bash
govplane policies add-rule -w ./governance \
  --policy-key auth-login-authenticate \
  --rule-file ./deny-retries.json
```

```
Rule added to auth-login-authenticate: deny-after-five-failures

Priority: 100
Effect: deny
```

The `5` now lives in a document you can review, version and change without a deploy. See [Conditional Rules](/docs/documentation/basic-concepts/conditional-rules.md) for the `when` vocabulary.
{% endstep %}

{% step %}

## Validate

```bash
govplane validate -w ./governance
```

```
✓ governance/policy-drafts.json is valid

Type:     draft
Policies: 1
Rules:    1
Schema:   1.0
```

{% endstep %}

{% step %}

## Build and sign

Generate a signing key once:

```bash
openssl ecparam -genkey -name prime256v1 -noout -out governance/keys/signing.pem
openssl ec -in governance/keys/signing.pem -pubout -out governance/keys/signing.pub.pem
```

```bash
govplane build -w ./governance --signed \
  --signing-algorithm ECDSA_SHA_256 \
  --ecdsa-private-key ./keys/signing.pem \
  --signing-key-id release-2026-08
```

```
Govplane Build

Output:
  Bundle: governance/policy-bundle.json
  Schema: 1
  Env: prod
  Bundle version: 1

Integrity:
  Checksum: sha256:b65a57eab43aa8b697ae4a56a276bb38ee4d7f77d545b690cfea7bb403bdf37f
  ETag: "b65a57eab43aa8b697ae4a56a276bb38ee4d7f77d545b690cfea7bb403bdf37f"

Signature:
  Enabled: yes
  Algorithm: ECDSA_SHA_256
  Key ID: release-2026-08

Result:
  Build completed successfully
```

{% hint style="info" %}
`--ecdsa-private-key` is resolved from the **working folder** — hence `./keys/signing.pem`, not `./governance/keys/signing.pem`. `--public-key` on `inspect` resolves from your **current directory** instead. When in doubt, use absolute paths.
{% endhint %}

The build also reports two warnings about `orgId` and `projectId` being unset. **For a local bundle that is fine** — the SDK does not read them, and `validate` accepts a bundle without them. Set them only if the bundle is destined for Govplane Cloud.
{% endstep %}

{% step %}

## Prove it behaves

```bash
govplane simulate -w ./governance \
  --service auth --resource login --action authenticate \
  --context '{"failedAttempts":6}'
```

```
Decision:
  decision: deny
  reason: rule

Match:
  Policy: auth-login-authenticate
  Rule: deny-after-five-failures
```

And the other branch, with the reasoning shown:

```bash
govplane simulate -w ./governance \
  --service auth --resource login --action authenticate \
  --context '{"failedAttempts":2}' --trace full
```

```
Decision:
  decision: allow
  reason: default

Match:
  No rule matched this target.
  The default effect of "auth-login-authenticate" applied.

Evaluation trace:
  Policies seen: 1
  Rules seen:    1
  Matched:       0

  Rules considered:
    auth-login-authenticate / deny-after-five-failures  priority 100  skipped (when_false)

  Selected:
    auth-login-authenticate / __default__ (priority -1, effect allow)
```

`skipped (when_false)` is the useful line: the rule *aimed* at this target and its condition did not hold, so the policy default answered.
{% endstep %}

{% step %}

## Verify the signature

```bash
govplane inspect -w ./governance --signature --public-key ./governance/keys/signing.pub.pem
```

```
Signature verification

Status:
  Valid

Algorithm:
  ECDSA_SHA_256

Key ID:
  release-2026-08
```

{% endstep %}

{% step %}

## Ship it

Copy `policy-bundle.json` and the **public** key to your application, and point the SDK at them:

```ts
import { createLocalClient } from '@govplane/runtime-sdk';
import { readFileSync } from 'node:fs';

const govplane = await createLocalClient({
  bundlePath: './governance/policy-bundle.json',
  verify: {
    algorithm: 'ECDSA_SHA_256',
    publicKey: readFileSync('./governance/keys/signing.pub.pem', 'utf8'),
  },
});
```

```js
govplane.status();
// {
//   origin: 'local',
//   bundleVersion: 1,
//   checksum: 'sha256:b65a57eab43aa8b697ae4a56a276bb38ee4d7f77d545b690cfea7bb403bdf37f',
//   env: 'prod',
//   signature: { status: 'verified', algorithm: 'ECDSA_SHA_256', keyId: 'release-2026-08' },
//   policies: 1
// }
```

The decisions match `simulate` exactly, because it is the same engine:

```js
const T = { service: 'auth', resource: 'login', action: 'authenticate' };

govplane.evaluate({ target: T, context: { failedAttempts: 6 } });
// { decision: 'deny', reason: 'rule', policyKey: 'auth-login-authenticate', ruleId: 'deny-after-five-failures' }

govplane.evaluate({ target: T, context: { failedAttempts: 2 } });
// { decision: 'allow', reason: 'default', policyKey: 'auth-login-authenticate' }
```

The application code becomes:

```ts
const decision = govplane.evaluate({
  target: { service: 'auth', resource: 'login', action: 'authenticate' },
  context: { failedAttempts },
});
if (decision.decision === 'deny') throw new Error('locked');
```

No `5` anywhere. Changing the threshold is a rebuilt bundle, not a release.
{% endstep %}

{% step %}

## Keep it honest in CI

```yaml
env:
  GOVPLANE_HOME: ${{ github.workspace }}/.govplane
  GOVPLANE_LICENSE: ${{ secrets.GOVPLANE_LICENSE }}

steps:
  - run: npm install --global @govplane/cli @govplane/cli-toolkit
  - run: govplane analyze --source ./src --check -w ./governance   # new evaluate() with no policy → fail
  - run: govplane validate --strict -w ./governance
  - run: govplane simulate --suite ./governance/simulations/auth.json -w ./governance
```

A suite turns the two checks from step 6 into regression tests:

```json
{
  "name": "auth",
  "scenarios": [
    {
      "name": "locked out after five failures",
      "target": { "service": "auth", "resource": "login", "action": "authenticate" },
      "context": { "failedAttempts": 6 },
      "expected": { "decision": "deny", "ruleId": "deny-after-five-failures" }
    },
    {
      "name": "normal login proceeds",
      "target": { "service": "auth", "resource": "login", "action": "authenticate" },
      "context": { "failedAttempts": 2 },
      "expected": { "decision": "allow", "reason": "default" }
    }
  ]
}
```

```
Govplane Simulation

Input: governance/policy-bundle.json

pass  locked out after five failures
      auth / login / authenticate → deny (rule)
pass  normal login proceeds
      auth / login / authenticate → allow (default)

Summary:
  Scenarios: 2
  Passed: 2
  Failed: 0
  Duration: 2ms
```

A failed expectation exits `1` and says exactly what changed:

```
Scenario failed: locked out after five failures

  Expected decision: allow
  Actual   decision: deny
```

{% endstep %}
{% endstepper %}

## What you ended up with

| Artefact                | Purpose                                            |
| ----------------------- | -------------------------------------------------- |
| `policy-drafts.json`    | What you edit — reviewed like code                 |
| `policy-bundle.json`    | What you ship — deterministic, checksummed, signed |
| `keys/signing.pem`      | Private. Never leaves the build machine            |
| `keys/signing.pub.pem`  | Public. Commit it, ship it                         |
| `simulations/auth.json` | Policy regression tests                            |

## Next

* [CLI Toolkit Commands](/docs/documentation/cli/cli-toolkit-commands.md) — every command in detail
* [Configuring the Toolkit](/docs/documentation/cli/configuring-the-cli-toolkit.md) — pin this in configuration instead of flags
* [SDK — Best Practices](/docs/documentation/sdk-for-javascript-node.js/best-practices.md) — running it in production
* [Policies and Rules](/docs/documentation/basic-concepts/policies-and-rules.md) — the concepts underneath


---

# 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/cli/practical-example-from-start-to-end.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.
