> For the complete documentation index, see [llms.txt](https://he-said.tdreyno.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://he-said.tdreyno.com/package-guides/abac-guide.md).

# ABAC Guide

This guide covers the ABAC package in `@tdreyno/he-said/abac`.

ABAC here means:

* You define `approve(...)` and `deny(...)` rules.
* Rules are built from core algebra constraints.
* You evaluate with `can(actionToken, { user, resource, environment })`.

## Getting Started

```ts
import {
  action,
  actionIs,
  approve,
  deny,
  enforcer,
  eq,
  eqEnv,
  failure,
  ge,
  policy,
} from "@tdreyno/he-said/abac"

type User = {
  id: string
  department: string
  clearance: number
  suspended: boolean
}

type Document = {
  ownerId: string
  department: string
  sensitivity: number
}

type Environment = {
  isBusinessHours: boolean
}

const READ = action("read")

const RULE_DENY_SUSPENDED = failure("Suspended users cannot access documents.")
const RULE_DENY_AFTER_HOURS = failure("Access blocked outside allowed hours.")

const denySuspended = deny(
  eq((user: User) => user.suspended, true),
  {
    failure: RULE_DENY_SUSPENDED,
  },
)

const denyAfterHours = deny(
  [actionIs(READ), eqEnv(environment => environment.isBusinessHours, false)],
  { failure: RULE_DENY_AFTER_HOURS, name: "after-hours" },
)

const approveRead = approve([
  actionIs(READ),
  eq(
    (user: User) => user.department,
    (resource: Document) => resource.department,
  ),
  ge(
    (user: User) => user.clearance,
    (resource: Document) => resource.sensitivity,
  ),
])

const authz = enforcer(policy(denySuspended, denyAfterHours, approveRead))

const decision = await authz.can(READ, {
  user: {
    id: "u1",
    department: "engineering",
    clearance: 4,
    suspended: false,
  },
  resource: {
    ownerId: "u2",
    department: "engineering",
    sensitivity: 2,
  },
  environment: {
    isBusinessHours: true,
  },
})

console.log(decision.allowed)
```

## Core Concepts

### Actions Are Identity Tokens

Actions are symbols, compared by reference:

```ts
const READ = action("read")
const UPDATE = action("update")
```

`action("read")` called twice creates two different tokens.

### Rules

* `approve(ruleOrRules, options?)`
* `deny(ruleOrRules, options?)`

`Rule[]` is normalized to `and(...rules)`.

### Failures

Create reusable failure tokens:

```ts
const RULE_DENY_SUSPENDED = failure("Suspended users cannot access documents.")
```

Attach to deny (or approve) rules:

```ts
const denySuspended = deny(
  eq((user: User) => user.suspended, true),
  {
    failure: RULE_DENY_SUSPENDED,
  },
)
```

### Policy and Evaluation

Policies are variadic:

```ts
const p = policy(ruleA, ruleB, ruleC)
```

Evaluate with `can`:

```ts
const decision = await authz.can(READ, { user, resource, environment })
```

## Authoring Patterns

### 1. Direct Rule

```ts
const denySuspended = deny(eq((user: User) => user.suspended, true))
```

### 2. Rule Array (AND shorthand)

```ts
const approveRead = approve([
  actionIs(READ),
  eq(
    (user: User) => user.department,
    (resource: Document) => resource.department,
  ),
])
```

### 3. Priority and Name Metadata

```ts
const denyAfterHours = deny(rule, {
  name: "after-hours",
  priority: 100,
  failure: RULE_DENY_AFTER_HOURS,
})
```

Higher priority evaluates first. Same priority ties are resolved with deny first.

### 4. Core-Algebra Fluent Composition

You can keep ABAC ergonomics and still compose rules with core algebra operators.

```ts
import { and, atLeast, not, or } from "@tdreyno/he-said"
import {
  action,
  actionIs,
  approve,
  deny,
  enforcer,
  eq,
  eqEnv,
  ge,
  policy,
} from "@tdreyno/he-said/abac"

const READ = action("read")

const sameDepartment = eq(
  (user: User) => user.department,
  (resource: Document) => resource.department,
)

const isOwner = eq(
  (user: User) => user.id,
  (resource: Document) => resource.ownerId,
)

const sufficientClearance = ge(
  (user: User) => user.clearance,
  (resource: Document) => resource.sensitivity,
)

const duringBusinessHours = eqEnv(
  (environment: Environment) => environment.isBusinessHours,
  true,
)

const approveRead = approve(
  and(
    actionIs(READ),
    not(eq((resource: Document) => resource.archived, true)),
    duringBusinessHours,
    or(
      isOwner,
      and(sameDepartment, sufficientClearance),
      atLeast(2, sameDepartment, isOwner, sufficientClearance),
    ),
  ),
)

const authz = enforcer(policy(denySuspended, approveRead))
```

Full runnable example:

* `src/abac/examples/example-core-algebra-fluent.ts`

### 5. SQL-compilable ABAC expressions

When you want ABAC rules to plan through the Postgres adapter, use predicate expressions (`attr(...)`, `eq(...)`, `ge(...)`) and relation atoms in `approve(...)` / `deny(...)`.

```ts
import { attr, eq, ge } from "@tdreyno/he-said"
import { approve, deny, resourceTerm, userTerm } from "@tdreyno/he-said/abac"

const denySuspended = deny(eq(attr(userTerm<User>(), "suspended"), true))

const approveRead = approve([
  eq(
    attr(userTerm<User>(), "department"),
    attr(resourceTerm<Document>(), "department"),
  ),
  ge(
    attr(userTerm<User>(), "clearance"),
    attr(resourceTerm<Document>(), "sensitivity"),
  ),
])
```

Closures still work for in-memory rules; they fail loudly on Postgres, where JavaScript unary predicates are not SQL-compilable.

See [**ABAC on Postgres**](https://github.com/tdreyno/he-said/blob/main/docs/abac-postgres-guide.md) for a complete in-memory and Postgres-backed policy walkthrough.

## Decision and Trace

A decision includes:

* `allowed`
* optional `failureToken`
* optional `reason`
* `trace` with checked and matched rule entries

Trace entries include stable rule reference tokens and optional names.


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://he-said.tdreyno.com/package-guides/abac-guide.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
