Skip to content

16.1 — oneOf / anyOf branch mapping #163

Description

@vrabbi

Phase: Phase 16 — Engine and strategy expansion
Proposal: docs/proposals/next-phases.md §16
Priority: P3 · Size: XL


Problem

oneOf, anyOf, allOf, and $ref nodes are treated opaquely: Analyze does not flatten through them, and an uncovered leaf inside one is reported with the construct name rather than being reasoned about. Transforming across a oneOf branch is documented as out of scope.

Union-typed fields are common in mature APIs — "one of s3, gcs, or azure config" is an ordinary thing for a platform XRD to express — and today a migration that touches one has no option but jsonPatch, which is always lossy and unverifiable.

Goal

A union-typed field can be converted with a strategy the engine can reason about.

Non-goals

Full JSON Schema union semantics. Specifically not in scope: anyOf where multiple branches validate simultaneously, or allOf composition. Target the common, tractable case — a discriminated union where exactly one branch applies — and reject the rest explicitly rather than half-supporting it.

Design

A branchMap strategy: given a oneOf on both sides, map branch i on the hub to branch j on the spoke, with an optional per-branch nested rule list for when the branches' internals also differ.

Open design questions to settle before implementation, written up in this issue:

  1. How is the active branch identified at runtime? A discriminator field is the tractable case. Structural matching (validate the object against each branch and pick the one that passes) is more general but needs a full validator on the hot path, which is a meaningful cost in the admission critical path.
  2. What happens when no branch matches, or several do? A hard runtime error is consistent with the project's fail-closed posture and with how arrayToMapByKey treats a duplicate key.
  3. Lossiness. Branch counts may differ; a hub branch with no spoke counterpart is a loss and must be declared.
  4. Coverage accounting. The leftover-field scan has to understand that a branch's leaves are claimed by the branch mapping, or every union will report as uncovered.

(4) is likely the largest piece of work and is where the estimate comes from.

Acceptance criteria

  • Design note answering the four questions, agreed before implementation
  • branchMap follows the full strategy contract: params type, op, compile resolver, webhook validation, CLI fixture, docs page (docs/contributing/adding-a-strategy.md)
  • Discriminated unions convert in both directions
  • No match / multiple matches is a hard runtime error with a clear message
  • Coverage accounting treats mapped branches as claimed
  • Lossiness verdict is correct and requires acknowledgement when branches do not correspond
  • docs/limitations.md updated to narrow the "treated opaquely" statement to what remains true

Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    phase-16Phase 16 - Engine and strategy expansionpriority/P3Nice to have; may slip a phasesize/XL> 2 weeks; consider splittingstrategiespkg/engine conversion strategy work

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions