Repository navigation
Name union arms from their schema node instead of their position #1409
Description
Taken out of #1392 at review so that PR only contains code the pipeline actually runs. @KonstantinMirin wrote it.
What it is
scripts/union_arm_names.py names a root-level union arm from its schema node instead of its position:
- Classify first. An arm that adds no new property is a validation constraint and creates no class. Bare
$refarms and scalar arms also create no class. - Name it using the first of these that is unique among its siblings:
- the arm's
title; - the parent name plus the PascalCased discriminator
const; - the parent name plus the arm's full sorted
requiredset.
- the arm's
- Otherwise refuse with
UnnameableArmError. It never falls back to a number.
tests/test_union_arm_names.py checks the rule against the pinned bundle. That includes the two response arms it can't name today.
Results on the pinned bundle: 557 root-level non-null arms.
- 169 named by title, 82 by const, 27 by required set.
- 278 create no class: 96 ref, 10 scalar, 172 validation.
- 1 refused:
content-standards/get-content-standards-response.jsononeOf[0], which has no title. Upstream fix: Both oneOf arms of get-content-standards-response carry no title, so generators name them by position adcp#7913.
Where the code is
On branch claude/issue-1409-union-arm-naming: #1392's head plus nothing else, before the extraction commit. It came from commits fcbf4229 and 5d705ca8. Those commits touch only scripts/union_arm_names.py and tests/test_union_arm_names.py. The design write-up is the "Union arm names" section of docs/design/type-generation.md on that branch.
What landing it needs
The rule isn't wired in yet. Arms are still named by position in restore_response_variant_aliases (f"{self.base}{index}"), using a hand-written 22-row table and a hardcoded BuildCreativeResponse reordering. Wiring it in is a one-line swap at class_names. The rest is two decisions about the public surface:
- The unnameable arm. If it's still unnameable, the build goes red as soon as the swap goes in. Either wait for Both oneOf arms of get-content-standards-response carry no title, so generators name them by position adcp#7913, or deliberately take that class off the flat surface and leave it reachable only by its domain path.
- The 38 publicly exported names ending in numbers become a breaking rename. That needs a deprecation shim and a migration table, ideally in a 9.x beta.
Also: test_no_derived_name_is_positional checks the rule against the JSON schemas, not the generated tree. A test that reads the generated classes has to land with the wiring.
Activity
chore(codegen): move the unwired union-arm naming rule to adcontextpr…
bokelley commentedon Oct 6, 2026
Triage
Classification: Codegen enhancement
Bucket(s): client (eventual wiring touches the public types surface in
src/adcp/)Status: deferred
My take: This issue was deliberately extracted from #1392 to keep that PR scoped to code the pipeline actually runs. The naming rule (
scripts/union_arm_names.py) and its tests exist on branchclaude/issue-1409-union-arm-naming(built on #1392's head), so the natural sequencing is: #1392 merges → this follows. Two open questions noted in the body also need human decisions before the wiring lands: (1) whether to wait for adcontextprotocol/adcp#7913 or deliberately remove the one unnameable arm from the flat surface; (2) how to handle the 38 publicly exported positional names that become a breaking rename — the body suggests a deprecation shim + migration table in a 9.x beta, which requires a versioning decision.Blocked-on: #1392 — resurfaces on merge
Triaged by Claude Code. Session: https://claude.ai/code/session_01Ga2v9ZdA4auwbwZxihBWfF
Generated by Claude Code
refactor(codegen)!: make the generated domain tree the one public typ…