As Configuration API says, each module needs to define its configuration schema in files in the config/schema directory under the top-level module directory. That is used by Drupal core to validate the configuration file and verify it's not corrupted.
For example, the Book module uses the following schema, for its configuration object (core/modules/book/config/schema/book.schema.yml).
# Schema for the configuration files of the book module.
book.settings:
type: config_object
label: 'Book settings'
mapping:
allowed_types:
type: sequence
label: 'Content types allowed in book outlines'
sequence:
type: string
label: 'Content type'
block:
type: mapping
label: 'Block'
mapping:
navigation:
type: mapping
label: 'Navigation'
mapping:
mode:
type: string
label: 'Mode'
child_type:
type: string
label: 'Content type for child pages'
block.settings.book_navigation:
type: block_settings
label: 'Book navigation block'
mapping:
block_mode:
type: string
label: 'Block display mode'
That is different from its configuration file, which is located in core/modules/book/config/installbook.settings.yml.
allowed_types:
- book
block:
navigation:
mode: 'all pages'
child_type: book
The code that throws that exception is contained in the ConfigSchemaChecker
class, used to implement an event subscriber that is invoked when a configuration object is saved.
public function onConfigSave(ConfigCrudEvent $event) {
// Only validate configuration if in the default collection. Other
// collections may have incomplete configuration (for example language
// overrides only). These are not valid in themselves.
$saved_config = $event->getConfig();
if ($saved_config->getStorage()->getCollectionName() != StorageInterface::DEFAULT_COLLECTION) {
return;
}
$name = $saved_config->getName();
$data = $saved_config->get();
$checksum = Crypt::hashBase64(serialize($data));
if (!in_array($name, $this->exclude) && !isset($this->checked[$name . ':' . $checksum])) {
$this->checked[$name . ':' . $checksum] = TRUE;
$errors = $this->checkConfigSchema($this->typedManager, $name, $data);
if ($errors === FALSE) {
throw new SchemaIncompleteException("No schema for {$name}");
}
elseif (is_array($errors)) {
$text_errors = [];
foreach ($errors as $key => $error) {
$text_errors[] = new FormattableMarkup('@key @error', [
'@key' => $key,
'@error' => $error,
]);
}
throw new SchemaIncompleteException("Schema errors for {$name} with the following errors: " . implode(', ', $text_errors));
}
}
}