diff --git a/loopx/semantics/vocabulary_v0.json b/loopx/semantics/vocabulary_v0.json index 203c8ddf9f..f601ce47ca 100644 --- a/loopx/semantics/vocabulary_v0.json +++ b/loopx/semantics/vocabulary_v0.json @@ -388,6 +388,40 @@ "todo_decision_scope_projection_repair", "unsettled_host_turn_recovery" ], + "value_notes": { + "agent_monitor_only": "The lane is monitor-only for this agent: not actionable by the agent, advancement work is the blocked scope, and the scheduler holds it in an agent-monitor wait.", + "agent_workspace_repair": "Workspace repair is allowed, and it outranks self-repair: the agent workspace is repaired before any further delivery. The name ends in _repair, so the route is repair_required.", + "automation_prompt_upgrade_required": "The installed automation prompt identity must be repaired first. It is one of the conditions that withholds autonomous_replan_decision_allowed, and the automation action is repair_automation_prompt_identity.", + "autonomous_replan_required": "An autonomous replan is required and its scope applies to this agent. It is one of REPLAN_ACTIONS, so the route is replan_required.", + "blocked_health": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "blocked_wait": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "boundary_projection_repair": "A required write scope is missing from the projected goal boundary; trigger required_write_scope_missing_from_goal_boundary.", + "capability_bridge_repair": "Capability repair is allowed once workspace repair and self-repair are not; the capability bridge is repaired before delivery.", + "control_plane_health_repair": "Stall repair raised by a health blocker; recommended_mode repair_control_plane_health. One of the five self-repair spend actions that stand in for the generic control_plane_repair.", + "control_plane_projection_repair": "Stall repair for a lane left waiting with no owner projection; trigger waiting_without_owner_projection.", + "control_plane_repair": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "coordinate_task_bundle": "Replaces normal_run when a ready task-orchestration contract makes this lane the coordinator: admitted or explicitly selected peer lanes are activated or resumed before its own worker-lane delivery.", + "external_evidence_observe": "An external-evidence monitor requires a read-only observation before any quiet no-op.", + "governed_capability_intent": "A governed capability intent is pending. It is the only action that routes to capability_action_required, and only when the intent projection matches the envelope goal and agent and names a command; a mismatch is a contract_error instead.", + "heartbeat_receipt_write_failed": "Writing the heartbeat receipt failed. The decision skips with state blocked_health and waits on the agent.", + "heartbeat_settled_skip": "The current heartbeat identity is fully settled. The automation stays active but quiet so a new turn can select the successor, with no quota spend for the settled turn.", + "lark_inbox_reply_due": "A direct Lark question, bot mention, or verified reply to the bot is pending a reply, and normal delivery is allowed for that reply.", + "monitor_due": "A monitor poll is due; should_run and normal delivery follow the same monitor_due flag. Its not-due counterpart in the same branch is monitor_quiet_skip.", + "monitor_quiet_skip": "A monitor poll that is not due. The scheduler maps it to a monitor wait, and it spends no quota.", + "normal_run": "Normal delivery is allowed; the first branch of quota_effective_action. With delivery_allowed and must_attempt set it routes to ready_for_host.", + "operator_gate_notify": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "operator_inbox_material_review_due": "Captured operator-inbox material is pending bounded review, and normal delivery is allowed for that review.", + "outcome_floor_recovery": "Normal delivery is refused but recovery delivery is allowed, through the quota safe bypass of kind outcome_floor_recovery.", + "peer_coordination_blocked": "Peer coordination blocks this lane. The scheduler stops it rather than holding it in a monitor wait.", + "quota_skip": "The final fallback of quota_effective_action: no delivery is allowed, no repair applies, and no more specific blocked state matched.", + "runtime_user_gate_projection_repair": "A runtime capability user-gate projection needs repair; recommended_mode repair_user_gate_projection, blocked scope user_gate_projection.", + "scoped_user_gate_fallback": "A scoped user-gate fallback applies while replan decisions are not allowed. It replaces quota_skip, monitor_quiet_skip or an absent action, and obliges one non-gated fallback segment after the user-gate notice.", + "state_projection_gap_repair": "A state-projection gap on a candidate that should run or must attempt; trigger state_projection_gap.", + "terminal_no_followup": "The Goal is terminal with no follow-up. The controller reads it as the terminal_action partition, so with no prior receipt the disposition is terminal, and the scheduler stops.", + "throttled_skip": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "todo_decision_scope_projection_repair": "A Todo user-gate scope is missing from the decision-scope projection; trigger user_gate_scope_projection_drift.", + "unsettled_host_turn_recovery": "A host Turn is unsettled and must be recovered before anything else: the selected Todo and action portfolio are dropped, should_run is set, and normal, recovery and self-repair delivery are all refused." + }, "producers": [ "loopx/cli_commands/quota.py::_apply_requested_quota_action_selection_preflight", "loopx/control_plane/quota/decision_summary.py::_task_orchestration_effective_action", @@ -409,13 +443,6 @@ "loopx/control_plane/todos/user_gate.py::apply_scoped_user_gate_fallback_projection" ], "compatibility_only": {}, - "value_notes": { - "blocked_health": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", - "blocked_wait": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", - "control_plane_repair": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", - "operator_gate_notify": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", - "throttled_skip": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions." - }, "return_producers": [ "loopx/control_plane/quota/decision_summary.py::quota_effective_action", "loopx/control_plane/quota/decision_summary.py::_task_orchestration_effective_action" diff --git a/tests/architecture/test_semantic_vocabulary_drift.py b/tests/architecture/test_semantic_vocabulary_drift.py index 9855b8257e..85db015c82 100644 --- a/tests/architecture/test_semantic_vocabulary_drift.py +++ b/tests/architecture/test_semantic_vocabulary_drift.py @@ -387,3 +387,23 @@ def test_live_inventory_ignores_missing_or_stale_reports(tmp_path, monkeypatch, for name in ("first", "second")] with pytest.raises(smoke["Drift"], match="same_runtime_forks grew"): smoke["check_inventory"](registry, sources + duplicate) + + +@pytest.mark.parametrize('name', ['effective_action', 'lease_action']) +def test_remaining_kernel_values_each_carry_a_note(name): + """The two kernel vocabularies that are not Turn control flow still need notes. + + ``effective_action`` is the overloaded should-run slot M1 is due to split, so + a value here is only legible once the registry says which condition produces + it; ``lease_action`` is legacy and every value is compatibility-only, which + is exactly the kind of disposition a reader cannot infer from the name. The + note is required in the diff that adds a value, not afterwards. + """ + smoke = runpy.run_path(str(SMOKE)) + vocabulary = smoke['load_registry']()['vocabularies'][name] + notes = vocabulary.get('value_notes', {}) + undocumented = [ + value for value in vocabulary['values'] + if not str(notes.get(value) or '').strip() + ] + assert not undocumented, f'{name}: values with no value_notes entry: {undocumented}'