# kix.keep

{/* Maintainer note: this page was written ahead of the reference section
    being properly built out (most sibling pages are still stubs). Align its
    layout and conventions with the rest of kix-helpers once that section
    gets its real authoring pass. */}

```nix
kix.keep container
```

Marks a container "never collapse to absent". Use it in constructors that
build a presence-meaningful field (an `emptyDir` volume source, a label
selector) from optional arguments that may all be null.

## Argument and result

| Input | Result |
|---|---|
| Attrset | The same attrset plus an additive `_kixKeep` marker. Resolved at `mkResource`; the marker never reaches `rawManifest`, the identity JSON, or the rendered output. |
| List | Normalized eagerly: null elements dropped; `[ ]` returned if nothing survives. No marker, because Nix merges replace lists wholesale, so a kept list cannot be re-filled with nulls later. |
| Anything else | Throws. |

## Normalization background

Rendering applies the collapse rule, bottom-up:

- `null` values are dropped.
- A container that had members and lost all of them collapses to null and
  is omitted; the collapse cascades upward through wrappers.
- A container written literally empty (`{ }`, `[ ]`) renders as `{}`/`[]`.
- Null list elements are dropped; an element that collapses entirely leaves
  its list.

`kix.keep` overrides the second point for one container: it renders `{}`
even when every member was null.

## Behavior under merges

| Operation | Result |
|---|---|
| `lib.recursiveUpdate kept { field = value; }` | Field merges in; keep is preserved. |
| `lib.recursiveUpdate kept { field = null; }` | Keep is preserved; the container still renders `{}` if everything collapses. |
| `attrs // { key = replacement; }` | Wholesale replacement discards the keep, matching the author's intent: the value was replaced. |
| `kix.keep (kix.keep x)` | Same as applying it once. |

## When it is not needed

- An explicit empty value (`emptyDir = { };`) renders as-is. No keep required.
- A constructor that filters its own nulls (`lib.optionalAttrs`) produces a
  literal empty and needs no keep. `kix.mount.emptyDir` works this way.
- CRD schema subtrees rarely need it: the collapse rule keeps their literal
  empties (`properties = { };`) as written.

## Related

- [Keep intentionally empty values](/docs/how-to/author-packages-and-clusters/keep-intentionally-empty-values/)
  for the task-oriented guide.
- [Built-in reliability rules](/docs/reference/scorecard/built-in-reliability-rules/):
  the `volumeHasSource` rule fails the build on volumes that would render
  without a source.