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:
- 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' } }
),
])
- Update the route handlers for both endpoints to include
type in the response payload:
- 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.
Follow up to #264565.
After the two PRs that added
meta:{id}(#270560) and migrated output schemas toschema.discriminatedUnion(#271407), 6 undiscriminated all-$refanyOfunions remain across 6 Fleet paths. Each requires schema reshaping beyond a simple rename.1.
POST /api/fleet/agents/{agentId}/rollback— response 2002.
POST /api/fleet/agents/{agentId}/privilege_level_change— response 200Schema:
ActionIdOrMessageSchemain server/types/rest_spec/agent.ts:25Current shape:
Problem: No shared property across branches —
actionIdandmessageare mutually exclusive.schema.discriminatedUnionrequires a property present on every branch with a uniqueschema.literalvalue.Fix required:
typediscriminator field to each branch in the schema:typein the response payload:postAgentRollbackHandlerin server/routes/agent/index.tspostAgentPrivilegeLevelChangeHandlerin the same filetypefield is additive in the response (no existing field removed), so it is non-breaking for users that tolerate unknown fields. However,oasdiffwill flag it as a response schema change — an allowlist entry may be needed.3.
POST /api/fleet/package_policies— requestBody4.
PUT /api/fleet/package_policies/{packagePolicyId}— requestBody5.
GET /api/fleet/package_policies— response (package_policy items)6.
POST /api/fleet/package_policies/upgrade/dryrun— response (dry-run items)Schema:
CreatePackagePolicyRequestSchema.bodyandUpdatePackagePolicyRequestSchema.bodyin 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
inputsas an array of objects (full format) or a simplified map (simplified format). Theformatquery parameter selects the interpretation but is not present in the body schema.Fix required (two options):
Option A — Add a synthetic
formatfield to the request body: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 onx-oas-discriminatorextension:Add a manual
x-discriminator-valueOAS 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
anyOfand close the gap item.Recommendation: Option C unless a future API version introduces an explicit
formatfield in the body.7.
PUT /api/fleet/outputs/{outputId}— requestBody (UpdateOutputSchema)Schema:
UpdateOutputSchemainserver/types/models/output.ts:343
Problem: Each
*UpdateSchemabranch declarestype: schema.maybe(schema.literal(...))—typeis optional in update payloads so callers can update individual fields without re-specifying the output type. Makingtyperequired is a breaking API change.Fix required:
Make
typerequired inPUT /api/fleet/outputs/{outputId}requests, then migrateUpdateOutputSchematoschema.discriminatedUnion('type', [...]). This is a deliberate API contract change requiring coordination with the Terraform provider team (@elastic/terraform-provider) and an allowlist entry incheck_api_contracts.Alternative: Introduce a new
PUTendpoint version that requirestype, deprecate the current one.