1313 * Auto-generates overlays/required_field_fixes.overlays.yaml from the merged
1414 * OAS bundles (output/kibana.yaml and output/kibana.serverless.yaml).
1515 *
16- * Root cause: schema.maybe() in @kbn/config-schema emits x-oas-optional: true
17- * on the resolved type schema, but the OAS generator still includes the field
18- * in the parent schema's required array. This script detects every such
19- * occurrence and emits a remove + update overlay action pair to fix it.
16+ * Detects two categories of wrongly-required fields and emits a remove + update
17+ * overlay action pair for each affected schema:
18+ *
19+ * 1. x-oas-optional fields — schema.maybe() in @kbn/config-schema emits
20+ * x-oas-optional: true on the resolved type schema, but the OAS generator
21+ * still includes the field in the parent schema's required array.
22+ *
23+ * 2. default-value fields — schema.object/array/etc. with a defaultValue option
24+ * emits a `default:` on the resolved schema, making the field optional at
25+ * runtime (callers may omit it and the server applies the default), but the
26+ * OAS generator still lists it in required.
2027 *
2128 * The generated file is committed and applied early in the api-docs-overlay
2229 * pipeline, before any hand-authored overlays run.
@@ -38,47 +45,80 @@ const OUTPUT_FILE = path.join(OAS_DOCS_DIR, 'overlays', 'required_field_fixes.ov
3845
3946// ─── helpers ──────────────────────────────────────────────────────────────────
4047
48+ /**
49+ * Load an OAS spec from disk. Supports both YAML (.yaml/.yml) and JSON (.json).
50+ * Returns null if the file does not exist.
51+ */
4152function loadSpec ( filePath ) {
4253 if ( ! fs . existsSync ( filePath ) ) return null ;
43- return yaml . load ( fs . readFileSync ( filePath , 'utf8' ) ) ;
54+ const raw = fs . readFileSync ( filePath , 'utf8' ) ;
55+ if ( filePath . endsWith ( '.json' ) ) return JSON . parse ( raw ) ;
56+ return yaml . load ( raw ) ;
4457}
4558
4659/**
47- * Returns the schema name if a $ref points to an x-oas-optional component schema.
60+ * Returns true if a property schema is optional due to x-oas-optional: true
61+ * (emitted by schema.maybe()) on its resolved type.
4862 *
49- * Limitation: this only detects optional fields that are expressed as a $ref to a
50- * named component schema that carries x-oas-optional: true. It does NOT detect
51- * x-oas-optional: true set inline on a property schema (i.e. no $ref). As of writing,
52- * ~30 inline occurrences exist in the bundles (e.g. fleet package policy inputs around
53- * line 29633 of kibana.yaml), but none sit inside a parent required array, so there is
54- * no active bug to fix. If that changes, extend this function to also check
63+ * Note: only detects the $ref case. Inline x-oas-optional (no $ref) does exist
64+ * (~30 occurrences in the bundles) but none currently sit inside a parent required
65+ * array, so there is no active bug. If that changes, also check
5566 * propSchema['x-oas-optional'] directly.
5667 */
57- function optionalRefName ( propSchema , optionalSchemas ) {
58- if ( ! propSchema ?. $ref ) return null ;
68+ function isXOasOptional ( propSchema , optionalSchemas ) {
69+ if ( ! propSchema ?. $ref ) return false ;
5970 const m = propSchema . $ref . match ( / ^ # \/ c o m p o n e n t s \/ s c h e m a s \/ ( .+ ) $ / ) ;
60- return m && optionalSchemas . has ( m [ 1 ] ) ? m [ 1 ] : null ;
71+ return m ? optionalSchemas . has ( m [ 1 ] ) : false ;
72+ }
73+
74+ /**
75+ * Returns true if a property schema is optional because its resolved type
76+ * declares a default value (emitted by the defaultValue option in
77+ * @kbn /config-schema). Such fields can be omitted by callers; the server
78+ * applies the default. Checks both inline schemas and $ref targets.
79+ */
80+ function hasDefaultValue ( propSchema , defaultSchemas ) {
81+ if ( ! propSchema ) return false ;
82+ if ( propSchema . default !== undefined ) return true ;
83+ if ( propSchema . $ref ) {
84+ const m = propSchema . $ref . match ( / ^ # \/ c o m p o n e n t s \/ s c h e m a s \/ ( .+ ) $ / ) ;
85+ return m ? defaultSchemas . has ( m [ 1 ] ) : false ;
86+ }
87+ return false ;
6188}
6289
6390/** Walk a spec object recursively, collecting required-field bugs. */
6491function collectBugs ( spec ) {
92+ const componentSchemas = spec . components ?. schemas || { } ;
93+
94+ // Category 1: schemas marked x-oas-optional (from schema.maybe())
6595 const optionalSchemas = new Set ( ) ;
66- for ( const [ name , schema ] of Object . entries ( spec . components ?. schemas || { } ) ) {
96+ for ( const [ name , schema ] of Object . entries ( componentSchemas ) ) {
6797 if ( schema [ 'x-oas-optional' ] === true ) optionalSchemas . add ( name ) ;
6898 }
6999
100+ // Category 2: schemas that declare a default value (from defaultValue option)
101+ const defaultSchemas = new Set ( ) ;
102+ for ( const [ name , schema ] of Object . entries ( componentSchemas ) ) {
103+ if ( schema . default !== undefined ) defaultSchemas . add ( name ) ;
104+ }
105+
70106 const bugs = [ ] ; // { jsonpath, buggyFields, correctRequired }
71107
72108 function walk ( node , jsonpath ) {
73109 if ( ! node || typeof node !== 'object' || Array . isArray ( node ) ) return ;
74110
75111 if ( node . properties && Array . isArray ( node . required ) && node . required . length > 0 ) {
76- const buggy = node . required . filter ( ( f ) =>
77- optionalRefName ( node . properties [ f ] , optionalSchemas )
112+ const buggy = node . required . filter (
113+ ( f ) =>
114+ isXOasOptional ( node . properties [ f ] , optionalSchemas ) ||
115+ hasDefaultValue ( node . properties [ f ] , defaultSchemas )
78116 ) ;
79117 if ( buggy . length > 0 ) {
80118 const correct = node . required . filter (
81- ( f ) => ! optionalRefName ( node . properties [ f ] , optionalSchemas )
119+ ( f ) =>
120+ ! isXOasOptional ( node . properties [ f ] , optionalSchemas ) &&
121+ ! hasDefaultValue ( node . properties [ f ] , defaultSchemas )
82122 ) ;
83123 bugs . push ( { jsonpath, buggyFields : buggy , correctRequired : correct } ) ;
84124 }
@@ -117,11 +157,13 @@ function generateActions(bugs) {
117157 const target = toTarget ( jsonpath ) ;
118158 lines . push ( ` - target: "${ target } .required"` ) ;
119159 lines . push (
120- ` description: "Remove x-oas-optional fields from required: ${ buggyFields . join ( ', ' ) } "`
160+ ` description: "Remove wrongly-required fields (x-oas-optional or has default): ${ buggyFields . join (
161+ ', '
162+ ) } "`
121163 ) ;
122164 lines . push ( ' remove: true' ) ;
123165 lines . push ( ` - target: "${ target } "` ) ;
124- lines . push ( ' description: "Restore required array without x-oas- optional fields"' ) ;
166+ lines . push ( ' description: "Restore required array without optional fields"' ) ;
125167 if ( correctRequired . length === 0 ) {
126168 lines . push ( ' update:' ) ;
127169 lines . push ( ' required: []' ) ;
@@ -169,10 +211,14 @@ function buildOverlayFile(statefulBugs, serverlessBugs) {
169211 '# THIS FILE IS AUTO-GENERATED — DO NOT EDIT BY HAND' ,
170212 `# Generated by: node oas_docs/scripts/generate_required_field_fixes.js` ,
171213 '#' ,
172- '# Root cause: schema.maybe() in @kbn/config-schema emits x-oas-optional: true' ,
173- '# on the resolved type schema, but the OAS generator still includes the field' ,
174- '# in the parent required array. Each fix below uses remove + update because' ,
175- "# the Bump overlay engine appends arrays on 'update' alone rather than replacing them." ,
214+ '# Fixes two categories of wrongly-required fields in the OAS bundles:' ,
215+ '# 1. x-oas-optional — schema.maybe() emits x-oas-optional: true on the resolved' ,
216+ '# type schema, but the OAS generator still lists the field in required.' ,
217+ '# 2. default-value — schema.object/etc. with defaultValue emits default: on the' ,
218+ '# resolved schema (the field is optional at runtime), but the OAS generator' ,
219+ '# still lists it in required.' ,
220+ '# Each fix uses remove + update because the Bump overlay engine appends arrays' ,
221+ "# on 'update' alone rather than replacing them." ,
176222 '#' ,
177223 `# ${ allBugs . length } schema(s) affected.` ,
178224 '' ,
0 commit comments