diff --git a/ops/dashboard/src/nanobot_ops_dashboard/app.py b/ops/dashboard/src/nanobot_ops_dashboard/app.py index 37a7a39..4686b58 100644 --- a/ops/dashboard/src/nanobot_ops_dashboard/app.py +++ b/ops/dashboard/src/nanobot_ops_dashboard/app.py @@ -560,6 +560,16 @@ def _mission_control_summary(*, context: dict, control_plane: dict | None, curre if not next_action_label: next_action_label = 'inspect canonical state and continue the next bounded self-improvement cycle' + autonomy_reasons = autonomy.get('reasons') if isinstance(autonomy.get('reasons'), list) else [] + autonomy_is_healthy_progress = ( + autonomy.get('state') == 'healthy_progress' + and not autonomy_reasons + and not autonomy_blocking_summary + ) + if autonomy_is_healthy_progress: + blocker_reason = None + blocker_source = 'autonomy_verdict' + blocker_state = 'none' if blocker_reason and blocker_reason not in {'unknown', 'none', 'clear'}: blocker_state = 'blocked' @@ -567,7 +577,9 @@ def _mission_control_summary(*, context: dict, control_plane: dict | None, curre blocker_state = autonomy.get('state') material_state = material.get('state') - if not material_state: + if autonomy_is_healthy_progress and (material.get('healthy_autonomy_allowed') or material.get('proof_count')): + material_state = 'available' + elif not material_state: material_state = 'available' if material.get('healthy_autonomy_allowed') or material.get('proof_count') else 'missing' elif material_state in {'unavailable', 'missing_current_material_progress'}: material_state = 'missing' diff --git a/ops/dashboard/tests/test_app.py b/ops/dashboard/tests/test_app.py index ebdf27e..cbe2bd3 100644 --- a/ops/dashboard/tests/test_app.py +++ b/ops/dashboard/tests/test_app.py @@ -495,6 +495,35 @@ def test_mission_control_names_concrete_blocker_from_autonomy_summary_when_curre assert payload['current_blocker']['source'] == 'promotion_replay_readiness' +def test_mission_control_healthy_autonomy_ignores_stale_current_blocker(): + payload = _mission_control_summary( + context=_minimal_mission_context(), + control_plane={}, + current_blocker={ + 'reason': 'active synthesized-improvement review lane remains bounded while awaiting materialization pressure', + 'failure_class': 'active synthesized-improvement review lane remains bounded while awaiting materialization pressure', + 'blocked_next_step': 'continue_active_lane', + 'source': 'stale_control_plane', + }, + material_progress={'schema_version': 'material-progress-v1', 'state': 'blocked', 'healthy_autonomy_allowed': True, 'proof_count': 1}, + runtime_parity={'state': 'healthy'}, + autonomy_verdict={ + 'state': 'healthy_progress', + 'reasons': [], + 'historical_reasons': ['same_task_streak'], + 'recommended_next_action': 'ready_for_policy_review', + 'blocking_summary': None, + }, + hypotheses_visibility={}, + experiment_visibility={}, + subagent_visibility={}, + analytics={}, + ) + + assert payload['headline'] == 'Healthy progress: material proof is available' + assert payload['current_blocker']['state'] == 'none' + + def test_mission_control_does_not_count_blocked_subagent_result_as_material_progress(): payload = _mission_control_summary( context=_minimal_mission_context(),