# Resource Type Inheritance

## Overview

Resource type inheritance allows custom resource types to derive from existing
types, inheriting their field definitions, CLI arguments, DAG constraints, and
tool bindings. A child type can override or extend its parent's configuration
while remaining compatible with tools and handlers that target the parent type.

This feature was introduced in [ADR-042](../adr/ADR-042-resource-type-inheritance.md)
to support polymorphic tool matching and reduce configuration duplication across
related resource types.

**Key benefits:**

- Define specialised resource types without repeating shared configuration.
- Tools bound to a parent type automatically match resources of any subtype.
- Handler resolution falls back through the inheritance chain when a
  type-specific handler is not registered.
- DAG queries respect the type hierarchy, returning resources whose type is a
  subtype of the requested ancestor.

## The `inherits` Field

A resource type declares its parent by setting the `inherits` field to the name
of an existing resource type:

```yaml
name: myorg/python-repo
inherits: git-checkout
resource_kind: physical
sandbox_strategy: git_worktree
description: A Git checkout containing a Python project.
cli_args:
  - name: python-version
    type: string
    required: false
    description: Expected Python version.
```

In this example `myorg/python-repo` inherits all fields from `git-checkout` and
adds a `python-version` CLI argument.

The `inherits` field is optional. When omitted the type is a root type with no
parent.

## Inheritance Chain Rules

The following five rules govern inheritance chains. Violations are reported by
`validate_chain()` and prevent the type from being registered.

### 1. Single Inheritance Only

A resource type may declare at most one parent via `inherits`. Multiple
inheritance is not supported.

### 2. Maximum Depth of 5 Levels

The inheritance chain from a type to its root ancestor must not exceed 5 levels
(i.e., at most 4 `inherits` hops). Deeper chains raise
`ResourceTypeInheritanceDepthError`.

```
root -> A -> B -> C -> D      # depth 5 — OK
root -> A -> B -> C -> D -> E # depth 6 — rejected
```

### 3. No Circular Inheritance

An inheritance chain must not contain cycles. If registering type `A` with
`inherits: B` would create a cycle (e.g., `B` already inherits from `A`,
directly or transitively), `ResourceTypeCircularInheritanceError` is raised.

### 4. Built-in Types May Not Inherit from Custom Types

Built-in resource types (`built_in: true`) are not permitted to set `inherits`
to a custom (namespaced) type. Custom types may inherit from built-in types or
from other custom types.

### 5. Cannot Remove a Parent While Subtypes Exist

A resource type's `inherits` field cannot be cleared or changed to a different
parent while other registered types list it as their parent. Attempting to do so
raises `ResourceTypeParentRemovalError`. Remove or re-parent all subtypes first.

## Field Resolution

When a type declares `inherits`, its effective configuration is computed by
overlaying the child's explicitly declared scalar fields on top of the parent's
resolved fields (which may themselves be inherited). This is a simple
**last-writer-wins** override for scalar values:

| Field              | Resolution                                         |
|--------------------|----------------------------------------------------|
| `description`      | Child value wins if set, otherwise parent's value. |
| `resource_kind`    | Child value wins if set, otherwise parent's value. |
| `sandbox_strategy` | Child value wins if set, otherwise parent's value. |
| `user_addable`     | Child value wins if set, otherwise parent's value. |
| `handler`          | Child value wins if set, otherwise parent's value. |
| `capabilities`     | Child value wins if set, otherwise parent's value. |
| `auto_discovery`   | Child value wins if set, otherwise parent's value. |
| `equivalence`      | Child value wins if set, otherwise parent's value. |

The `name`, `built_in`, and `inherits` fields are never inherited — they are
intrinsic to the declaring type.

## Collection Field Merging

Collection fields use **additive merging** by default: the child's entries are
appended to the parent's resolved entries (duplicates are deduplicated). To
replace the parent's collection entirely, set the corresponding
`<field>_replace` flag to `true`.

### `cli_args`

Parent CLI arguments are included in the child type. The child may add new
arguments or override a parent argument by declaring an argument with the same
`name`. To discard all parent arguments and use only the child's definitions:

```yaml
cli_args_replace: true
cli_args:
  - name: custom-arg
    type: string
```

### `child_types`

The child type's allowed `child_types` list is the union of the parent's and
the child's declarations. To replace:

```yaml
child_types_replace: true
child_types:
  - myorg/sub-resource
```

### `parent_types`

Behaves identically to `child_types` — additive merge with
`parent_types_replace: true` for full replacement.

## Polymorphism Guarantees

### Tool Binding

`ToolRegistry.find_tools_for_resource()` returns tools bound to the resource's
own type **and** every ancestor type in its inheritance chain. A tool registered
for `git-checkout` will match resources of type `myorg/python-repo` if
`myorg/python-repo` inherits from `git-checkout`.

### Handler Resolution

When resolving a handler for a resource, the system walks the inheritance chain
from the most specific type to the root. The first type in the chain that has a
registered handler is used. This allows specialised types to override behaviour
while falling back to the parent handler when no override exists.

### DAG Queries

Resource DAG queries that filter by type respect the inheritance hierarchy. A
query for all resources of type `git-checkout` also returns resources whose type
is a subtype of `git-checkout`. Use `is_subtype_of()` for explicit type checks.

## API Reference

Public functions exported from `cleveragents.resource.inheritance`:

| Function | Signature | Description |
|----------|-----------|-------------|
| `resolve_inheritance_chain` | `(type_name: str, type_registry: dict) -> list[str]` | Returns the ordered inheritance chain from `type_name` to the root ancestor (inclusive). Raises `ResourceTypeCircularInheritanceError` on cycles and `ResourceTypeParentNotFoundError` if an ancestor is missing. |
| `validate_chain` | `(type_name: str, inherits: str \| None, type_registry: dict, is_built_in: bool) -> list[str]` | Validates the proposed inheritance chain and returns the resolved chain (child to root) on success. Raises on failure. Checks all five chain rules. |
| `is_subtype_of` | `(type_name: str, ancestor_name: str, type_registry: dict) -> bool` | Returns `True` if `type_name` is equal to or a subtype of `ancestor_name`. |
| `resolve_fields` | `(type_name: str, type_registry: dict) -> dict` | Returns the fully resolved field dictionary for `type_name` after applying inheritance chain field resolution and collection merging. |
| `find_subtypes` | `(ancestor_name: str, type_registry: dict) -> list[str]` | Returns all registered type names that are subtypes of `ancestor_name` (direct and transitive). Does **not** include `ancestor_name` itself. |

## Exceptions

The following exceptions are defined in `cleveragents.resource.inheritance` and
raised when inheritance rules are violated:

| Exception | Raised When |
|-----------|-------------|
| `ResourceTypeInheritanceDepthError` | The inheritance chain exceeds the maximum depth of 5 levels. |
| `ResourceTypeCircularInheritanceError` | A circular inheritance chain is detected. |
| `ResourceTypeParentNotFoundError` | The type named in `inherits` is not present in the type registry. |
| `ResourceTypeParentRemovalError` | An attempt is made to remove or change a type's parent while subtypes still reference it. |

All exceptions inherit from `ValueError` and include the type name and
chain details in their message for diagnostics.

## CLI

### `agents resource type list`

The tabular output includes an **Inherits** column showing the direct parent
type name (blank for root types):

```
Name                   Kind      Sandbox          Inherits
────────────────────── ───────── ──────────────── ──────────────
git-checkout           physical  git_worktree
fs-directory           physical  copy_on_write
myorg/python-repo      physical  git_worktree     git-checkout
```

### `agents resource type show <name>`

Displays the full resolved configuration for the type, including an
**Inheritance Chain** section:

```
Name:              myorg/python-repo
Inherits:          git-checkout
Inheritance Chain: myorg/python-repo -> git-checkout
Resource Kind:     physical
Sandbox Strategy:  git_worktree
...
```

## Database

The `resource_types` table includes an `inherits` column:

| Column     | Type          | Nullable | Description                              |
|------------|---------------|----------|------------------------------------------|
| `inherits` | `String(255)` | Yes      | Name of the parent resource type, or `NULL` for root types. |

This column is added by the Alembic migration
`m6_004_resource_type_inherits`. The column is indexed to support efficient
subtype lookups. Foreign-key integrity is enforced at the application layer
(not via a database constraint) because type registration order during
migrations may not satisfy FK ordering requirements.
