@@ -174,4 +174,93 @@ public function test_core_get_environment_info_executes(): void {
174174 $ this ->assertArrayHasKey ( 'wp_version ' , $ ability_data );
175175 $ this ->assertSame ( $ environment , $ ability_data ['environment ' ] );
176176 }
177+
178+ /**
179+ * Tests that all core ability schemas only use valid JSON Schema keywords.
180+ *
181+ * This prevents regressions where invalid keywords like 'examples' are used
182+ * in schema properties (not valid in JSON Schema draft-04 used by WordPress).
183+ *
184+ * @ticket 64384
185+ */
186+ public function test_core_abilities_schemas_use_only_valid_keywords (): void {
187+ $ allowed_keywords = rest_get_allowed_schema_keywords ();
188+ // Add 'required' which is valid at the property level for draft-04.
189+ $ allowed_keywords [] = 'required ' ;
190+
191+ $ abilities = wp_get_abilities ();
192+
193+ $ this ->assertNotEmpty ( $ abilities , 'Core abilities should be registered. ' );
194+
195+ foreach ( $ abilities as $ ability ) {
196+ $ this ->assert_schema_uses_valid_keywords (
197+ $ ability ->get_input_schema (),
198+ $ allowed_keywords ,
199+ $ ability ->get_name () . ' input_schema '
200+ );
201+ $ this ->assert_schema_uses_valid_keywords (
202+ $ ability ->get_output_schema (),
203+ $ allowed_keywords ,
204+ $ ability ->get_name () . ' output_schema '
205+ );
206+ }
207+ }
208+
209+ /**
210+ * Recursively validates that a schema only uses allowed keywords.
211+ *
212+ * @param array|null $schema The schema to validate.
213+ * @param string[] $allowed_keywords List of allowed schema keywords.
214+ * @param string $context Context for error messages.
215+ */
216+ private function assert_schema_uses_valid_keywords ( ?array $ schema , array $ allowed_keywords , string $ context ): void {
217+ if ( null === $ schema ) {
218+ return ;
219+ }
220+
221+ foreach ( $ schema as $ key => $ value ) {
222+ // Skip integer keys (array indices).
223+ if ( is_int ( $ key ) ) {
224+ continue ;
225+ }
226+
227+ // These keywords contain nested schemas that we recurse into.
228+ $ nesting_keywords = array ( 'properties ' , 'items ' , 'additionalProperties ' , 'patternProperties ' , 'anyOf ' , 'oneOf ' );
229+
230+ if ( ! in_array ( $ key , $ nesting_keywords , true ) && ! in_array ( $ key , $ allowed_keywords , true ) ) {
231+ $ this ->fail ( "Invalid schema keyword ' {$ key }' found in {$ context }. Valid keywords are: " . implode ( ', ' , $ allowed_keywords ) );
232+ }
233+
234+ // Recursively check nested schemas.
235+ if ( 'properties ' === $ key && is_array ( $ value ) ) {
236+ foreach ( $ value as $ prop_name => $ prop_schema ) {
237+ $ this ->assert_schema_uses_valid_keywords (
238+ $ prop_schema ,
239+ $ allowed_keywords ,
240+ "{$ context }.properties. {$ prop_name }"
241+ );
242+ }
243+ } elseif ( 'items ' === $ key && is_array ( $ value ) ) {
244+ $ this ->assert_schema_uses_valid_keywords (
245+ $ value ,
246+ $ allowed_keywords ,
247+ "{$ context }.items "
248+ );
249+ } elseif ( ( 'anyOf ' === $ key || 'oneOf ' === $ key ) && is_array ( $ value ) ) {
250+ foreach ( $ value as $ index => $ sub_schema ) {
251+ $ this ->assert_schema_uses_valid_keywords (
252+ $ sub_schema ,
253+ $ allowed_keywords ,
254+ "{$ context }. {$ key }[ {$ index }] "
255+ );
256+ }
257+ } elseif ( 'additionalProperties ' === $ key && is_array ( $ value ) ) {
258+ $ this ->assert_schema_uses_valid_keywords (
259+ $ value ,
260+ $ allowed_keywords ,
261+ "{$ context }.additionalProperties "
262+ );
263+ }
264+ }
265+ }
177266}
0 commit comments