<!-- HushSpec 1.0.0; guide; stable
releaseCommit: e771ec647b7f26a0a09ff852886eb8a91093bc58
docsCommit: 5a252dbf7eb46b507ac9493dff8a695abf5b5e9a
sourcePath: docs/src/merge-semantics.md
canonical: https://www.hushspec.org/docs/merge/
Relative links in this unmodified source body are relative to sourcePath.
-->

# Merge Semantics

HushSpec supports policy inheritance via the `extends` field. When a child policy extends a base, the `merge_strategy` controls how they combine.

The merge result is a resolved document: `extends` and `merge_strategy` are consumed during resolution and omitted from the merged output. Engines evaluate only resolved documents; evaluating a child whose `extends` has not been resolved silently drops the base policy.

Under every strategy, a child `metadata` object replaces the base's entirely; when the child omits `metadata`, the base's is kept.

## Strategies

### `deep_merge` (default)

Child rules override base rules at the individual rule-block level. If the child
defines `rules.egress`, it replaces the base's `rules.egress` entirely, including
its `when` condition. Rules not defined in the child are preserved from the base.

Extensions use their companion-spec merge rules under `deep_merge`:

- `posture` merges states by name and replaces transitions with the child's list
- `origins` merges profiles by `id` and preserves `default_behavior` when omitted
- `detection` merges subsections independently and preserves omitted subsection fields

### `merge`

For core `rules`, `merge` matches `deep_merge` in HushSpec v1.

For `extensions`, `merge` is shallower: if the child defines an extension block,
that block replaces the base extension block entirely. Unspecified extension
blocks are still preserved from the base.

### `replace`

The child document entirely replaces the base. No fields from the base are preserved.

## Example

Each YAML block below is a complete file; save both in the same directory.

```yaml
# base.yaml
hushspec: "1.0.0"
name: base
rules:
  egress:
    allow: ["a.com"]
    default: block
  forbidden_paths:
    patterns: ["**/.ssh/**"]
```

```yaml
# child.yaml
hushspec: "1.0.0"
name: child
extends: base.yaml
rules:
  egress:
    allow: ["b.com"]
    default: allow
```

Result: `egress` uses child's config (`b.com`, allow). `forbidden_paths` preserved from base.

## Resolution Order

Resolve from the oldest base toward the leaf, applying each child's strategy at
its hop. Three files `root.yaml -> team.yaml -> agent.yaml` do not concatenate
lists: each explicitly supplied rule block replaces that block from the previous
result. `extends` and `merge_strategy` disappear from the resolved output.
Use `h2h resolve agent.yaml` and review the result before evaluating it.

Cycles, missing bases, malformed pins, digest mismatches and chains beyond the
32-hop limit refuse the whole load. Do not recover by evaluating only the leaf.
See [core resolution](../../spec/hushspec-core.md#26-resolution).

## Strategy Comparison

| Child strategy | Core rule blocks | Extensions | Metadata |
| --- | --- | --- | --- |
| `deep_merge` | Replace supplied blocks; preserve others | Companion-specific merge | Replace whole object if supplied |
| `merge` | Same core behavior | Replace supplied extension blocks | Replace whole object if supplied |
| `replace` | Child only | Child only | Child only |

## Extension Merge Rules

For `deep_merge`, [posture](extensions/posture.md#merge-rules) replaces states by
name and the transition list; [origins](extensions/origins.md#merge-rules)
replaces profiles by ID; [detection](extensions/detection.md#merge-rules)
merges subsection fields. This inheritance behavior is different from origin
projection, which narrows a resolved policy for a request.

## Note on Merge Helpers

HushSpec does **not** support `additional_*` or `remove_*` fields. These are engine-specific features. If you need additive pattern management, use your engine's native format.
