Looks like the best way is to utilise the BlockedStatus method.
There is an example of its usage in the Postgres K8s charm. There isn’t a explicit error state in the API, and maintenance is explicitly not for errors so BlockedStatus is probably most appropriate.
Historically the lack of an error state is because if you are in a position to know an error has occurred then you should make a best effort to resolve that issue automagically. In the event that you’re unable to do so then you should try and handle the failure gracefully by putting the charm into a blocked state and allowing the user to resolve the issue.
The error state should typically be reserved for cases where the application has just failed outright and is beyond automated recovery or notification.
In practice this will mean that when you hit an error you will want to enhance your charm with the ability to automatically resolve the issue or by gracefully handling it. This will make the charm more robust over time and future users, yourself included , will have the knowledge of the failure handling codified.
Will the blocked state prevent the state machine to progress?
As I understand it, it will not. So as far as I can tell, I can’t easily prevent a state transition between let’s say config-changed -> start?
Also, the error state is very unexpressive since the user is always left in the dark.
Regardless how much the intent is to improve the charm quality, the user of it will be left in a dark place not even able to help out in the debug easily. That’s not helping to achieve that quality. Perhaps the opposite.
I would argue that a user encountering an error would be able to report on it, with a proper error message, and thereby contributing to the quality improvement rather than just be forced to perform a difficult debug session.