fix(display): a raising display() counts as a circuit-breaker failure (#707)

The first-frame dispatch (_dispatch_first_frame) now asks PluginExecutor.execute_display() to re-raise (raise_errors=True) and records a raise inside the executor as a breaker failure, with the original exception as last_error, instead of a success. The screen is still an empty pass and rotation is unchanged; a hung display() is still recorded once, as a hang. The run-loop golden trace plugin_error.json is regenerated (crashy now records health failures and is skipped by the breaker), and behaviour 7 is dropped from docs/RUN_LOOP_REDESIGN.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-10-01 14:51:38 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent a21650e746
commit 9edeb6da14
7 changed files with 361 additions and 12 deletions
+3 -2
View File
@@ -61,8 +61,9 @@ def scenario_all_empty(h: RunLoopHarness):
def scenario_plugin_error(h: RunLoopHarness):
# broken's dispatch raises (no display lock: loading failed part-way),
# so all its modes are skipped together; two failures open the breaker.
# crashy's display() raises inside the executor, which reports False:
# an empty pass ("raised"), not a failure, so its modes are not skipped.
# crashy's display() raises inside the executor: an empty pass
# ("raised") that also counts as a breaker failure, so after two raises
# it is skipped by the breaker. Its modes are not skipped together.
h.add_plugin(FakePlugin("clock", ["clock"], duration=10))
h.add_plugin(FakePlugin("broken", ["broken_a", "broken_b"], duration=10), lock=False)
h.add_plugin(FakePlugin("weather", ["weather"], duration=10))