> 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/basic-concepts/effects.md).

# Effects

An effect is the outcome of a rule. It is what the policy engine returns to your application when it evaluates a bundle.

### Effect Types

The Govplane policy engine recognises five effect types. They are applied in this fixed precedence order:

```
kill_switch  >  deny  >  throttle  >  allow  >  custom  >  deny-by-default
```

#### allow

Grants access. The request proceeds normally.

Bundle rule shape:

```json
{
  "id": "r_allow_viewers",
  "status": "active",
  "priority": 10,
  "target": { "service": "api", "resource": "reports", "action": "read" },
  "effect": { "type": "allow" }
}
```

Decision shape:

```ts
{ decision: "allow", reason: "rule", policyKey: "reports-policy", ruleId: "r_allow_viewers" }
```

#### deny

Blocks access.

Bundle rule shape:

```json
{
  "id": "r_deny_guests",
  "status": "active",
  "priority": 5,
  "target": { "service": "api", "resource": "admin", "action": "write" },
  "effect": { "type": "deny" }
}
```

Decision shape:

```ts
{ decision: "deny", reason: "rule", policyKey: "admin-policy", ruleId: "r_deny_guests" }
```

**Deny-by-default:** If no rule matches and no policy default applies, the SDK returns `{ decision: "deny", reason: "default" }` automatically. There is no `policyKey` or `ruleId` in this case.

#### throttle

Rate-limits the request. Your application is responsible for enforcing the limit; the SDK only signals the intent.

Bundle rule shape:

```json
{
  "id": "r_throttle_free",
  "status": "active",
  "priority": 20,
  "target": { "service": "api", "resource": "export", "action": "create" },
  "effect": {
    "type": "throttle",
    "throttle": {
      "limit": 10,
      "windowSeconds": 3600,
      "key": "tenant"
    }
  }
}
```

Decision shape:

```ts
{
  decision: "throttle",
  reason: "rule",
  policyKey: "export-policy",
  ruleId: "r_throttle_free",
  throttle: { limit: 10, windowSeconds: 3600, key: "tenant" }
}
```

{% hint style="info" %}
The SDK does not maintain counters. It signals the throttle intent and parameters. Your infrastructure (middleware, Redis, etc.) is responsible for tracking and enforcing the rate limit.
{% endhint %}

Handling a throttle:

```ts
if (result.decision === "throttle") {
  const allowed = await rateLimiter.check(
    result.throttle.key === "tenant" ? ctx.tenantId : ctx.userId,
    result.throttle.limit,
    result.throttle.windowSeconds,
  );
  if (!allowed) {
    return reply.status(429)
      .header("Retry-After", String(result.throttle.windowSeconds))
      .send({ error: "Too Many Requests" });
  }
  // within limit — proceed
}
```

**Strictest-wins rule:** When multiple throttle rules match, the engine selects the one with the lowest request-per-second rate (i.e. the most restrictive limit), not the one with the highest priority.

#### Full throttle handling example

In-memory enforcement example (no external dependencies):

```ts
// Simple sliding-window counter: bucketKey → { count, windowStart }
const throttleCounters = new Map<string, { count: number; windowStart: number }>();

function checkThrottle(limit: number, windowSeconds: number, bucketKey: string): boolean {
  const now = Date.now();
  const windowMs = windowSeconds * 1000;
  let entry = throttleCounters.get(bucketKey);

  if (!entry || now - entry.windowStart >= windowMs) {
    entry = { count: 0, windowStart: now };
    throttleCounters.set(bucketKey, entry);
  }

  if (entry.count >= limit) return false; // over limit
  entry.count += 1;
  return true; // within limit
}

// --- In your request handler ---
const result = client.evaluate({
  target:  { service: "api", resource: "export", action: "create" },
  context: { tenantId: "tenant_acme" },
});

if (result.decision === "throttle") {
  const { limit, windowSeconds, key } = result.throttle;

  // `key` is the bucketing dimension from the policy ("tenant", "user", "ip", …).
  // Combine it with the real identifier from your request context.
  const tenantId = "tenant_acme"; // read from your auth context in practice
  const bucketKey = `${key}:${tenantId}`; // e.g. "tenant:tenant_acme"

  if (!checkThrottle(limit, windowSeconds, bucketKey)) {
    return reply.status(429)
      .header("Retry-After", String(windowSeconds))
      .send({ error: "Too Many Requests", limit, windowSeconds });
  }
  // within limit — fall through and process the request
}
```

#### kill\_switch

Immediately blocks all traffic to a service. Designed for incidents and emergency shutdowns.

Bundle rule shape:

```json
{
  "id": "r_kill_payments",
  "status": "active",
  "priority": 1,
  "target": { "service": "payments", "resource": "*", "action": "*" },
  "effect": {
    "type": "kill_switch",
    "killSwitch": {
      "service": "payments",
      "reason": "Database degradation detected — incident INC-4421"
    }
  }
}
```

Decision shape:

```ts
{
  decision: "kill_switch",
  reason: "rule",
  policyKey: "payments-circuit-breaker",
  ruleId: "r_kill_payments",
  killSwitch: { service: "payments", reason: "Database degradation detected — incident INC-4421" }
}
```

Handling a kill switch:

```ts
if (result.decision === "kill_switch") {
  logger.error("kill_switch active", { service: result.killSwitch.service });
  return reply.status(503).send({
    error: "Service Unavailable",
    reason: result.killSwitch.reason,
  });
}
```

{% hint style="warning" %}
`kill_switch` has the highest precedence. It overrides any allow, deny, throttle, or custom decision from any other rule in any policy.
{% endhint %}

### custom

Returns an arbitrary string value — including a JSON-encoded object — defined in the bundle. Useful for feature flags, A/B variants, contextual metadata, or any structured response that does not fit the other effect types.

See Custom Effects for the full reference.

Decision shape (summary):

```ts
{
  decision: "custom",
  reason: "rule",
  policyKey: "feature-flags",
  ruleId: "r_flags_pro",
  value: "{\"newCheckout\": true, \"aiSearch\": false}",
  parsedValue: { newCheckout: true, aiSearch: false }  // present when parseCustomEffect is enabled
}
```


---

# 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/basic-concepts/effects.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.
