## DollarPaths utility

When you provide a typed `context`, value positions in condition builders accept `$`-prefixed references with full autocomplete. This is powered by the `DollarPaths` utility type:

```typescript
type DollarPaths<TContext> = `$${DotPaths<TContext>}`
// e.g. '$subject.id' | '$subject.roles' | '$subject.attributes.status' | '$resource.id' | ...
```

`DollarPaths<TContext>` maps every dot-path in your context into a `$`-prefixed template literal. These `$`-references are available on the value parameter of `.check()`, `.eq()`, `.neq()`, `.attr()`, `.resourceAttr()`, and `.env()`, anywhere you can compare against a dynamic value from the request context.

***

## Usage

```typescript
access
  .definePolicy('owner-only')
  .rule('deny-non-owner', (r) =>
    r
      .deny()
      .on('update', 'delete')
      .of('post')
      .when((w) =>
        w
          // Full autocomplete: '$subject.id', '$subject.roles', '$subject.attributes.status', ...
          .resourceAttr('ownerId', 'neq', '$subject.id')
          // Cross-attribute comparison
          .attr('status', 'eq', '$resource.attributes.status'),
      ),
  )
  .build()
```

No `as string` cast is needed. The value parameter accepts both literal values (e.g. `'draft'`) and `$`-references (e.g. `'$subject.id'`) in a single union type.

***

## How the type narrowing works

Value parameters use `ConditionValue` internally plus `FlexibleDollarPaths` at the method
signature level. Non-string types (like `number` for `env('hour', ...)`) pass through
unchanged, so passing a string where a number is expected is a compile error. The
`FlexibleDollarPaths<TContext>` union is added directly to each method signature, not
nested inside computed types, so the IDE can see and suggest the `$`-prefixed literals.
Optional properties (e.g. `yearsExperience?: number`) are handled correctly by stripping
`undefined` before type resolution.

The `& {}` intersection trick is used internally so that even when the literal type is `string`, autocomplete still shows `$`-prefixed suggestions. Without this, TypeScript would resolve to plain `string` and lose all `$`-suggestions.

***

## Where typed `$`-references are accepted

| Method | Right-hand value supports `$`? |
| --- | --- |
| `check(field, op, value)` | yes |
| `eq(field, value)` | yes |
| `neq(field, value)` | yes |
| `attr(key, op, value)` | yes |
| `resourceAttr(key, op, value)` | yes |
| `env(key, op, value)` | yes |
| `gt`, `gte`, `lt`, `lte` (numbers) | yes - `$path` resolving to a number works |
| `in`, `nin` (array values) | array literals only - `$path` to an array isn't auto-supported |
| `contains`, `not_contains` | yes |
| `starts_with`, `ends_with`, `matches` | yes (string-resolving paths) |
| `exists`, `not_exists` | no value parameter |

***

## Common patterns

### Owner check

```typescript
.when((w) => w.resourceAttr('ownerId', 'eq', '$subject.id'))
// Editor autocomplete shows: '$subject.id', '$subject.attributes.tier', '$resource.id', ...
```

Or use the shortcut:

```typescript
.when((w) => w.isOwner())
// Generates the same condition under the hood
```

### Cross-field equality

```typescript
.when((w) => w.attr('department', 'eq', '$resource.attributes.department'))
// User's department must match resource's department
```

### Self-action prevention

```typescript
.when((w) => w.check('resource.id', 'eq', '$subject.id'))
// Used with `deny()` to prevent users from acting on their own account record
```

### Scope match

```typescript
.when((w) => w.check('resource.attributes.scope', 'eq', '$scope'))
// Resource's scope attribute must equal the request scope
```

***

## Strict typing

When the field side resolves to a narrow union and the value side is a `$`-reference, TypeScript validates that the resolved type at the `$` path **could be assignable** to the field type:

```typescript
interface AppContext extends DefaultContext {
  subject: { attributes: { tier: 'free' | 'pro' } }
  resource: { attributes: { tier: 'free' | 'pro' } }
}

const access = defineIam({
  actions: ['read'] as const,
  resources: ['post'] as const,
  context: {} as AppContext,
})

// OK - both sides resolve to 'free' | 'pro'
access.when().check('resource.attributes.tier', 'eq', '$subject.attributes.tier')

// In practice the type system can't always cross-validate $-paths against field types
// - it relies on the union at the method signature level
```

For strong cross-path type checking, define narrow attribute types and avoid `string`/`AttributeValue` fallbacks where you care.

***

## When to drop down to `check()`

The `attr/resourceAttr/env` shortcuts are typed against narrow attribute keys. For arbitrary field paths, use `check()`:

```typescript
.when((w) => w.check('environment.region', 'eq', '$subject.attributes.homeRegion'))
```

This compiles even when `region` and `homeRegion` aren't typed in your context - `check()` accepts any string path. You lose the dot-path autocomplete but gain flexibility.