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:
posturemerges states by name and replaces transitions with the child's listoriginsmerges profiles byidand preservesdefault_behaviorwhen omitteddetectionmerges 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.
# base.yaml
hushspec: "1.0.0"
name: base
rules:
egress:
allow: ["a.com"]
default: block
forbidden_paths:
patterns: ["**/.ssh/**"]# child.yaml
hushspec: "1.0.0"
name: child
extends: base.yaml
rules:
egress:
allow: ["b.com"]
default: allowResult: 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.
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 replaces states by
name and the transition list; origins
replaces profiles by ID; detection
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.