Skip to content

[OAS Validation] Fix remaining anyOf unions in Fleet paths #271410

@criamico

Description

@criamico

Follow up to #264565.

After the two PRs that added meta:{id} (#270560) and migrated output schemas to schema.discriminatedUnion (#271407), 6 undiscriminated all-$ref anyOf unions remain across 6 Fleet paths. Each requires schema reshaping beyond a simple rename.


1. POST /api/fleet/agents/{agentId}/rollback — response 200

2. POST /api/fleet/agents/{agentId}/privilege_level_change — response 200

Schema: ActionIdOrMessageSchema in server/types/rest_spec/agent.ts:25

Current shape:

schema.oneOf([
  schema.object({ actionId: schema.string() }, { meta: { id: 'action_id_response' } }),
  schema.object({ message: schema.string() },  { meta: { id: 'action_message_response' } }),
])

Problem: No shared property across branches — actionId and message are mutually exclusive. schema.discriminatedUnion requires a property present on every branch with a unique schema.literal value.

Fix required:

  1. Add a synthetic type discriminator field to each branch in the schema:
    schema.discriminatedUnion('type', [
      schema.object(
        { type: schema.literal('actionId'), actionId: schema.string() },
        { meta: { id: 'action_id_response' } }
      ),
      schema.object(
        { type: schema.literal('message'), message: schema.string() },
        { meta: { id: 'action_message_response' } }
      ),
    ])
  2. Update the route handlers for both endpoints to include type in the response payload:
  3. The type field is additive in the response (no existing field removed), so it is non-breaking for users that tolerate unknown fields. However, oasdiff will flag it as a response schema change — an allowlist entry may be needed.

3. POST /api/fleet/package_policies — requestBody

4. PUT /api/fleet/package_policies/{packagePolicyId} — requestBody

5. GET /api/fleet/package_policies — response (package_policy items)

6. POST /api/fleet/package_policies/upgrade/dryrun — response (dry-run items)

Schema: CreatePackagePolicyRequestSchema.body and UpdatePackagePolicyRequestSchema.body in server/types/rest_spec/package_policy.ts,
both are schema.oneOf([CreatePackagePolicyRequestBodySchema, SimplifiedCreatePackagePolicyRequestBodySchema]).

Problem: The two branches represent two different input formats (full vs simplified), not tagged variants of the same type. There is no shared literal property that distinguishes them — the caller either provides inputs as an array of objects (full format) or a simplified map (simplified format). The format query parameter selects the interpretation but is not present in the body schema.

Fix required (two options):

Option A — Add a synthetic format field to the request body:

schema.discriminatedUnion('format', [
  CreatePackagePolicyRequestBodySchema.extends(
    { format: schema.literal('legacy') },
    { meta: { id: 'create_package_policy_request' } }
  ),
  SimplifiedCreatePackagePolicyRequestBodySchema.extends(
    { format: schema.literal('simplified') },
    { meta: { id: 'simplified_create_package_policy_request' } }
  ),
])

This is a breaking request body change — existing callers must now include format.
Not recommended without a deprecation period.

Option B — Keep schema.oneOf, rely on x-oas-discriminator extension:
Add a manual x-discriminator-value OAS extension annotation (if the generator supports it) to hint to downstream tooling without enforcing a runtime discriminator. This avoids the breaking change but requires OAS generator support.

Option C — Accept as permanently ineligible:
Document that full/simplified format variants cannot be discriminated at the schema level without a breaking change. Leave as anyOf and close the gap item.

Recommendation: Option C unless a future API version introduces an explicit format field in the body.


7. PUT /api/fleet/outputs/{outputId} — requestBody (UpdateOutputSchema)

Schema: UpdateOutputSchema in
server/types/models/output.ts:343

Problem: Each *UpdateSchema branch declares type: schema.maybe(schema.literal(...))type is optional in update payloads so callers can update individual fields without re-specifying the output type. Making type required is a breaking API change.

Fix required:
Make type required in PUT /api/fleet/outputs/{outputId} requests, then migrate UpdateOutputSchema to schema.discriminatedUnion('type', [...]). This is a deliberate API contract change requiring coordination with the Terraform provider team (@elastic/terraform-provider) and an allowlist entry in check_api_contracts.

Alternative: Introduce a new PUT endpoint version that requires type, deprecate the current one.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Team:FleetTeam label for Observability Data Collection Fleet team

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions