# ADR 0205 — Flat tool collisions retain structural alternatives

> Source: https://github.com/max-listov/stitchkit/blob/master/docs/decisions/0205-flat-tool-collisions-retain-structural-alternatives.md

---

# ADR 0205 — Flat tool collisions retain structural alternatives

- **Status:** Accepted — amends the no-nested-union collision rule of ADRs 0033
  and 0065; preserves the presentation/runtime boundary of ADR 0050.
- **Invariants:** I3, I8.

## Context

A field shared by discriminated-union variants can be an object in one variant
and an array in another. Advertising only `type: ['object', 'array']` discards
the object vocabulary and array `items`. A provider requiring an item schema
rejects the tool before execution. Empty `items` also hides the model's contract
when two array alternatives have different element structures.

## Decision

The shared presentation join retains distinct structural alternatives in a
property-level `anyOf` whenever the provable kinds include an object or array.
Each alternative retains its items, properties, required keys, bounds and key
policy. Structurally identical alternatives are deduplicated; shared outer
descriptions and conditional-required hints belong to the joined property.

The containing discriminated union still becomes one object. Its discriminator
is an enum and only fields required by every variant remain required. Plain
scalar collisions retain their conservative type/enum widening. An unprovable
kind remains unconstrained instead of acquiring a guessed structure.

This is a union of already projected schemas, without a Cartesian expansion or
a second recursive projection pass. Every value accepted by an original branch
is still accepted by the presentation; discriminator-to-field correlations are
intentionally absent there. The original Zod schema alone owns runtime parsing.

## Consequences

Flat mode guarantees an object for a structurally identifiable discriminated
union, not the absence of every nested `anyOf`. A consumer must accept nested
structural alternatives; disabling flatten globally is unnecessary. Manifest,
MCP and Agent use the same owning implementation. No public option, runtime
validator or transport-specific schema patch is added.

Tests exercise production manifests and mounted AI SDK schemas, nested arrays,
nullable and optional fields, both object shapes and runtime rejection of a
value belonging to the wrong discriminator branch.
