diff --git a/loopx/cli_commands/quota.py b/loopx/cli_commands/quota.py index 2e3d752737..7581d0e7f3 100644 --- a/loopx/cli_commands/quota.py +++ b/loopx/cli_commands/quota.py @@ -1,10 +1,12 @@ from __future__ import annotations import argparse +import sys import time from collections.abc import Callable, Mapping +from importlib import import_module from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any from .quota_presentation import ( _project_quota_cli_payload as _project_quota_cli_payload, @@ -21,20 +23,7 @@ ) from ..usage_goal import observe_quota_result -from ..capabilities.explore.composition_frontier import ( - project_live_explore_composition_frontier, -) -from ..capabilities.periodic_report.pending_intent import ( - periodic_report_pending_intent_interaction_hook, -) -from ..capabilities.repository_change_window import ( - repository_delivery_interaction_hook, -) from ..control_plane.coordination.local_authority import CanonicalAuthorityRequired -from ..control_plane.goals.first_party_host_admission import ( - capture_first_party_host_goal_ref, -) -from ..control_plane.goals.source_session_registry_state import exact_goal_ref from ..control_plane.quota.effect_program import SettlementIdentity from ..control_plane.quota.error_codes import ( CloseoutQueryUnavailableError, @@ -48,54 +37,6 @@ find_heartbeat_receipt, heartbeat_receipt_view, ) -from ..control_plane.quota.live_decision import build_live_quota_should_run_decision -from ..control_plane.quota.monitor_poll import find_quota_monitor_poll_turn -from ..control_plane.quota.settlement_cli import ( - attach_spend_settlement_result, - quota_rollout_details, - quota_rollout_replan_obligation_id, - quota_rollout_todo_id, - reconcile_existing_heartbeat_receipt_for_turn, - render_existing_heartbeat_receipt_payload, -) -from ..quota import ( - build_quota_plan, - record_quota_monitor_poll, - spend_quota_slot, - void_quota_slot, -) -from ..status import collect_status -from ..upgrade import resolve_codex_app_automation_rrule -from .lark_inbox import ( - build_lark_operator_inbox_urgency_projector, - dispatch_goal_lark_turn_start_hooks, -) -from .quota_action_selection import ( - RequestedQuotaActionSelection, - commit_requested_action_selection, - load_requested_quota_action_selection, - inline_action_selection_reentry_args, - reconcile_requested_quota_action_selection, -) -from .quota_context import ( - QuotaCommandContext, - prepare_quota_command_context, - validate_quota_command_context_request, -) -from .quota_failure_report import ( - QUOTA_EVENT_KINDS, - captured_quota_failure_view, - quota_decision_transport_failure_payload, - quota_failure_payload, - quota_validation_failure_payload, - should_log_quota, -) -from .quota_host_poll import attach_host_poll_receipt -from .quota_monitor_poll import ( - record_automatic_heartbeat_stall as _record_automatic_heartbeat_stall, - record_quota_monitor_poll_for_cli, - recover_quota_monitor_poll_for_cli, -) from .quota_registration import ( register_quota_command as register_quota_command, # noqa: PLC0414 ) @@ -103,7 +44,100 @@ attach_reward_memory_ingest_after_spend, attach_reward_memory_recall_after_should_run, ) -from .quota_scheduler_followup import build_scheduler_followup_payload + +if TYPE_CHECKING: + from .quota_action_selection import RequestedQuotaActionSelection + from .quota_context import QuotaCommandContext + +# Native context and replan-view routes return before any authority read, so +# authority, decision, and capability owners load on first use. Call sites +# resolve them through ``_export`` so module-level patches stay effective. +_LAZY_EXPORTS: dict[str, tuple[str, str]] = { + name: (module, name) + for module, names in { + "loopx.capabilities.explore.composition_frontier": ( + "project_live_explore_composition_frontier", + ), + "loopx.capabilities.periodic_report.pending_intent": ( + "periodic_report_pending_intent_interaction_hook", + ), + "loopx.capabilities.repository_change_window": ( + "repository_delivery_interaction_hook", + ), + "loopx.control_plane.quota.live_decision": ( + "build_live_quota_should_run_decision", + ), + "loopx.control_plane.quota.monitor_poll": ("find_quota_monitor_poll_turn",), + "loopx.control_plane.quota.settlement_cli": ( + "attach_spend_settlement_result", + "quota_rollout_details", + "quota_rollout_replan_obligation_id", + "quota_rollout_todo_id", + "reconcile_existing_heartbeat_receipt_for_turn", + "render_existing_heartbeat_receipt_payload", + ), + "loopx.quota": ( + "build_quota_plan", + "record_quota_monitor_poll", + "spend_quota_slot", + "void_quota_slot", + ), + "loopx.status": ("collect_status",), + "loopx.upgrade": ("resolve_codex_app_automation_rrule",), + "loopx.cli_commands.lark_inbox": ( + "build_lark_operator_inbox_urgency_projector", + ), + "loopx.cli_commands.quota_action_selection": ( + "commit_requested_action_selection", + "load_requested_quota_action_selection", + "inline_action_selection_reentry_args", + "reconcile_requested_quota_action_selection", + ), + "loopx.cli_commands.quota_context": ( + "prepare_quota_command_context", + "validate_quota_command_context_request", + ), + "loopx.cli_commands.quota_failure_report": ( + "QUOTA_EVENT_KINDS", + "captured_quota_failure_view", + "quota_decision_transport_failure_payload", + "quota_failure_payload", + "quota_validation_failure_payload", + "should_log_quota", + ), + "loopx.cli_commands.quota_host_poll": ("attach_host_poll_receipt",), + "loopx.cli_commands.quota_monitor_poll": ( + "record_quota_monitor_poll_for_cli", + "recover_quota_monitor_poll_for_cli", + ), + "loopx.cli_commands.quota_scheduler_followup": ( + "build_scheduler_followup_payload", + ), + }.items() + for name in names +} +_LAZY_EXPORTS["_record_automatic_heartbeat_stall"] = ( + "loopx.cli_commands.quota_monitor_poll", + "record_automatic_heartbeat_stall", +) + + +def __getattr__(name: str) -> Any: + owner = _LAZY_EXPORTS.get(name) + if owner is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + value = getattr(import_module(owner[0]), owner[1]) + globals()[name] = value + return value + + +def __dir__() -> list[str]: + return sorted(set(globals()) | set(_LAZY_EXPORTS)) + + +def _export(name: str) -> Any: + return getattr(sys.modules[__name__], name) + PrintPayload = Callable[ [dict[str, object], str, Callable[[dict[str, object]], str]], @@ -124,6 +158,8 @@ def _quota_goal_ref( getattr(args, "goal_instance_id", None) or "" ).strip() if goal_instance_id: + from ..control_plane.goals.source_session_registry_state import exact_goal_ref + return exact_goal_ref(goal_id, goal_instance_id) if args.quota_command not in { "should-run", @@ -135,6 +171,10 @@ def _quota_goal_ref( "void-slot", }: return None + from ..control_plane.goals.first_party_host_admission import ( + capture_first_party_host_goal_ref, + ) + return capture_first_party_host_goal_ref( registry_path=registry_path, goal_id=goal_id, @@ -175,9 +215,11 @@ def _dispatch_quota_turn_start_hooks( registry_path: Path, runtime_root_arg: str | None, ) -> tuple[dict[str, object] | None, bool]: - validate_quota_command_context_request(args) + _export("validate_quota_command_context_request")(args) if args.quota_command != "should-run": return None, False + from .lark_inbox import dispatch_goal_lark_turn_start_hooks + dispatch = dispatch_goal_lark_turn_start_hooks( registry_path=registry_path, runtime_root_arg=runtime_root_arg, @@ -245,13 +287,13 @@ def _prepare_quota_command_execution( registry_path=registry_path, runtime_root_arg=runtime_root_arg, ) - context = prepare_quota_command_context( + context = _export("prepare_quota_command_context")( args, registry_path=registry_path, runtime_root_arg=runtime_root_arg, - status_collector=collect_status, + status_collector=_export("collect_status"), operator_inbox_urgency_projector_factory=( - build_lark_operator_inbox_urgency_projector + _export("build_lark_operator_inbox_urgency_projector") ), force_projection_refresh=turn_start_mutated, ) @@ -282,10 +324,10 @@ def _emit_quota_result( """Capture the full decision before projecting and printing the CLI view.""" # Compose optional capability guidance after recovery has chosen the plan, # before capture/compaction. The quota decision owner stays capability-neutral. - from ..capabilities.explore.turn_context import project_settlement_attachment - cli = (payload.get("interaction_contract") or {}).get("cli_channel") if isinstance(cli, dict) and isinstance(cli.get("settlement_plan"), Mapping): + from ..capabilities.explore.turn_context import project_settlement_attachment + cli["settlement_plan"] = project_settlement_attachment( cli["settlement_plan"], registry_path=registry_path, runtime_root=context.runtime_root if context is not None else None, @@ -314,14 +356,14 @@ def _emit_quota_result( if args.quota_command == "should-run" and context is not None: # Capture the one liveness observation with the decision that produced # it. Presentation and evidence recovery must not record it again. - attach_host_poll_receipt( + _export("attach_host_poll_receipt")( context.status_payload, args, payload, registry_path=registry_path, ) try: capture_decision(capture_directory, payload) if (payload.get("ok") is False and context_delivery_enabled and capture_directory and args.format == "json" and not getattr(args, "defer_context_presentation", False)): - print_payload(captured_quota_failure_view(payload, capture_directory=capture_directory, + print_payload(_export("captured_quota_failure_view")(payload, capture_directory=capture_directory, max_page_output_bytes=getattr(args, "max_page_output_bytes", None)), args.format, _quota_renderer(args, failure=True)) return 1 @@ -331,7 +373,7 @@ def _emit_quota_result( # The guard already ran. Report that typed fact with the durable receipt # and saved decision, never a new collection failure that could replace # the capture (an inline reentry runs inside the caller's guard). - failure = quota_decision_transport_failure_payload( + failure = _export("quota_decision_transport_failure_payload")( args, payload, exc, capture_directory=capture_directory, ) print_payload(failure, args.format, _quota_renderer(args, failure=True)) @@ -397,7 +439,7 @@ def _emit_quota_result( ) # The original receipt survives even when the selected presentation # is a Turn envelope without the quota packet's receipt fields. - failure = quota_decision_transport_failure_payload( + failure = _export("quota_decision_transport_failure_payload")( args, captured_payload, exc, capture_directory=capture_directory, ) print_payload(failure, args.format, _quota_renderer(args, failure=True)) @@ -418,7 +460,7 @@ def _record_non_guard_quota_event( payload, registry_path=registry_path, runtime_root_arg=runtime_root_arg, - event_kind=QUOTA_EVENT_KINDS[args.quota_command], + event_kind=_export("QUOTA_EVENT_KINDS")[args.quota_command], goal_ref=goal_ref, agent_id=args.agent_id, todo_id=rollout_todo_id, @@ -443,7 +485,7 @@ def _record_non_guard_quota_event( ) if args.quota_command == "spend-slot" and spend_turn_instance_id: assert context is not None # A settlement requires a prepared authority context. - attach_spend_settlement_result( + _export("attach_spend_settlement_result")( payload, runtime_root=context.runtime_root, goal_id=args.goal_id, @@ -528,15 +570,15 @@ def handle_quota_command( ) if args.quota_command == "should-run": interaction_projection_hooks = ( - repository_delivery_interaction_hook(repo_path=Path.cwd()), - periodic_report_pending_intent_interaction_hook( + _export("repository_delivery_interaction_hook")(repo_path=Path.cwd()), + _export("periodic_report_pending_intent_interaction_hook")( registry_path=registry_path, runtime_root=runtime_root, goal_id=args.goal_id, agent_id=args.agent_id, ), ) - action_selection = load_requested_quota_action_selection( + action_selection = _export("load_requested_quota_action_selection")( args, runtime_root=runtime_root, turn_instance_id=heartbeat_turn_id, @@ -544,7 +586,7 @@ def handle_quota_command( retained_todo_id=retained_action_selection_todo_id, ) heartbeat_receipt_existing = action_selection.receipt - payload = build_live_quota_should_run_decision( + payload = _export("build_live_quota_should_run_decision")( status_payload, goal_id=args.goal_id, agent_id=args.agent_id, @@ -558,11 +600,11 @@ def handle_quota_command( codex_app_current_rrule=args.app_automation_current_rrule, registry_path=registry_path, runtime_root=runtime_root, - host_observation_resolver=resolve_codex_app_automation_rrule, + host_observation_resolver=_export("resolve_codex_app_automation_rrule"), scheduler_execution_context=scheduler_context, operator_inbox_urgency_projector=operator_inbox_urgency_projector, bounded_research_frontier_projector=( - project_live_explore_composition_frontier + _export("project_live_explore_composition_frontier") ), receipt_bound_todo_id=action_selection.receipt_bound_todo_id, requested_action_todo_id=( @@ -585,7 +627,7 @@ def handle_quota_command( ) _attach_turn_start_hook_dispatch(payload, turn_start_hook_dispatch) action_selection_preflight = ( - reconcile_requested_quota_action_selection( + _export("reconcile_requested_quota_action_selection")( payload, args, registry_path=registry_path, @@ -596,7 +638,7 @@ def handle_quota_command( ) action_selection_preflight_failed = action_selection_preflight.rejected if action_selection_preflight.rejected: - reentry_args = inline_action_selection_reentry_args( + reentry_args = _export("inline_action_selection_reentry_args")( payload, args, selection=action_selection, preflight=action_selection_preflight, turn_instance_id=heartbeat_turn_id, @@ -631,7 +673,7 @@ def handle_quota_command( heartbeat_receipt_existing_appended, heartbeat_stall_observation, heartbeat_receipt_ready, - ) = reconcile_existing_heartbeat_receipt_for_turn( + ) = _export("reconcile_existing_heartbeat_receipt_for_turn")( payload, args, runtime_root=runtime_root, @@ -645,7 +687,7 @@ def handle_quota_command( status_payload, cache_metadata, heartbeat_stall_observation, - ) = _record_automatic_heartbeat_stall( + ) = _export("_record_automatic_heartbeat_stall")( payload, status_payload, args, @@ -657,15 +699,15 @@ def handle_quota_command( action_selection=action_selection, cache_metadata=cache_metadata, goal_ref=goal_ref, - find_monitor_poll_turn=find_quota_monitor_poll_turn, - monitor_poll_recorder=record_quota_monitor_poll, - status_collector=collect_status, - decision_builder=build_live_quota_should_run_decision, - host_observation_resolver=resolve_codex_app_automation_rrule, + find_monitor_poll_turn=_export("find_quota_monitor_poll_turn"), + monitor_poll_recorder=_export("record_quota_monitor_poll"), + status_collector=_export("collect_status"), + decision_builder=_export("build_live_quota_should_run_decision"), + host_observation_resolver=_export("resolve_codex_app_automation_rrule"), ) heartbeat_receipt_ready = True elif args.quota_command == "monitor-poll": - payload = record_quota_monitor_poll_for_cli( + payload = _export("record_quota_monitor_poll_for_cli")( args, status_payload=status_payload, registry_path=registry_path, @@ -674,8 +716,8 @@ def handle_quota_command( scheduler_execution_context=scheduler_context, operator_inbox_urgency_projector=operator_inbox_urgency_projector, goal_ref=goal_ref, - monitor_poll_recorder=record_quota_monitor_poll, - status_reloader=lambda: collect_status( + monitor_poll_recorder=_export("record_quota_monitor_poll"), + status_reloader=lambda: _export("collect_status")( registry_path=registry_path, runtime_root_override=runtime_root_arg, limit=status_limit, @@ -688,7 +730,7 @@ def handle_quota_command( "scheduler-ack-current", "scheduler-fail-current", }: - payload = build_scheduler_followup_payload( + payload = _export("build_scheduler_followup_payload")( status_payload, args, registry_path=registry_path, @@ -699,7 +741,7 @@ def handle_quota_command( goal_ref=goal_ref, ) elif args.quota_command == "spend-slot": - payload = spend_quota_slot( + payload = _export("spend_quota_slot")( status_payload, goal_id=args.goal_id, slots=args.slots, @@ -716,7 +758,7 @@ def handle_quota_command( goal_ref=goal_ref, ) elif args.quota_command == "void-slot": - payload = void_quota_slot( + payload = _export("void_quota_slot")( status_payload, goal_id=args.goal_id, voided_run_generated_at=args.void_generated_at, @@ -729,17 +771,17 @@ def handle_quota_command( goal_ref=goal_ref, ) else: - payload = build_quota_plan(status_payload, mode=args.quota_command) + payload = _export("build_quota_plan")(status_payload, mode=args.quota_command) if cache_metadata: payload["status_projection_cache"] = cache_metadata except CanonicalAuthorityRequired as exc: - payload = recover_quota_monitor_poll_for_cli( + payload = _export("recover_quota_monitor_poll_for_cli")( args, registry_path=registry_path, runtime_root_arg=runtime_root_arg, error=exc, goal_ref_loader=lambda: _quota_goal_ref(args, registry_path=registry_path), ) except QuotaCommandValidationError as exc: # Only typed CLI validation diagnostics are public-safe by contract. - payload = quota_validation_failure_payload( + payload = _export("quota_validation_failure_payload")( args, exc, registry_path=registry_path, @@ -751,20 +793,20 @@ def handle_quota_command( # guard and the identity diagnostic instead of calling it failed IO. if isinstance(exc, HeartbeatReceiptIdentityConflictError): action_selection_preflight_failed = True - payload = quota_failure_payload( + payload = _export("quota_failure_payload")( args, registry_path=registry_path, runtime_root_arg=runtime_root_arg, error=exc, ) - if should_log_quota(args.quota_command, payload): + if _export("should_log_quota")(args.quota_command, payload): spend_turn_instance_id = _effective_spend_turn_instance_id( payload, heartbeat_turn_id=heartbeat_turn_id, ) - rollout_todo_id = quota_rollout_todo_id(payload, args) - rollout_replan_obligation_id = quota_rollout_replan_obligation_id(payload, args) - rollout_details = quota_rollout_details( + rollout_todo_id = _export("quota_rollout_todo_id")(payload, args) + rollout_replan_obligation_id = _export("quota_rollout_replan_obligation_id")(payload, args) + rollout_details = _export("quota_rollout_details")( payload, args, todo_id=rollout_todo_id, @@ -773,7 +815,7 @@ def handle_quota_command( if heartbeat_turn_id and args.quota_command == "should-run": if action_selection_preflight_failed or closeout_query_unavailable: if heartbeat_receipt_existing: - render_existing_heartbeat_receipt_payload( + _export("render_existing_heartbeat_receipt_payload")( payload, receipt=heartbeat_receipt_existing, turn_instance_id=heartbeat_turn_id, @@ -798,14 +840,14 @@ def handle_quota_command( ), ) elif heartbeat_receipt_existing: - render_existing_heartbeat_receipt_payload( + _export("render_existing_heartbeat_receipt_payload")( payload, receipt=heartbeat_receipt_existing, turn_instance_id=heartbeat_turn_id, status=heartbeat_receipt_existing_status, appended=heartbeat_receipt_existing_appended, ) - commit_requested_action_selection( + _export("commit_requested_action_selection")( payload, requested_todo_id=( action_selection.requested_todo_id @@ -884,7 +926,7 @@ def handle_quota_command( if rollout_event.get("appended") else "replayed", ) - commit_requested_action_selection( + _export("commit_requested_action_selection")( payload, requested_todo_id=( action_selection.requested_todo_id diff --git a/loopx/cli_commands/quota_monitor_poll.py b/loopx/cli_commands/quota_monitor_poll.py index d3efa7890f..a12e1c3be8 100644 --- a/loopx/cli_commands/quota_monitor_poll.py +++ b/loopx/cli_commands/quota_monitor_poll.py @@ -3,15 +3,11 @@ import argparse from collections.abc import Callable, Mapping from pathlib import Path +from typing import TYPE_CHECKING from ..control_plane.coordination.local_authority import CanonicalAuthorityRequired from ..control_plane.capability_hooks import InteractionProjectionHookRegistration from ..control_plane.quota.effective_action import EffectiveAction -from .quota_context import QuotaCommandContext -from .quota_action_selection import RequestedQuotaActionSelection -from ..capabilities.explore.composition_frontier import ( - project_live_explore_composition_frontier, -) from ..control_plane.quota.error_codes import HeartbeatReceiptIdentityConflictError from ..control_plane.quota.heartbeat_receipt import ( find_heartbeat_receipt, @@ -21,6 +17,10 @@ SchedulerExecutionContextResolution, ) +if TYPE_CHECKING: + from .quota_action_selection import RequestedQuotaActionSelection + from .quota_context import QuotaCommandContext + def _receipt_bound_monitor_todo_id( args: argparse.Namespace, @@ -118,6 +118,10 @@ def record_quota_monitor_poll_for_cli( settlement_todo_id=settlement_todo_id, after_decision=lambda _status: before, render_markdown=lambda _payload: "", ) + from ..capabilities.explore.composition_frontier import ( + project_live_explore_composition_frontier, + ) + return monitor_poll_recorder( status_payload, **arguments, available_capabilities=args.available_capabilities, @@ -201,6 +205,10 @@ def record_automatic_heartbeat_stall( and existing_stall is None ): return payload, status_payload, cache_metadata, "not_applicable" + from ..capabilities.explore.composition_frontier import ( + project_live_explore_composition_frontier, + ) + poll = monitor_poll_recorder( status_payload, goal_id=args.goal_id, diff --git a/loopx/cli_commands/quota_presentation.py b/loopx/cli_commands/quota_presentation.py index 0041fa5f9f..c2248602a7 100644 --- a/loopx/cli_commands/quota_presentation.py +++ b/loopx/cli_commands/quota_presentation.py @@ -6,33 +6,29 @@ from ..control_plane.effect_runtime import EffectRuntimeRejected from ..control_plane.quota.turn_envelope import build_turn_envelope -from ..control_plane.quota.cli_projection import ( - compact_quota_monitor_poll_cli_payload, - compact_quota_plan_cli_payload, - compact_quota_should_run_cli_payload, -) -from ..presentation.renderers.quota_event_markdown import ( - render_quota_monitor_poll_markdown, - render_quota_slot_preview_markdown, -) - -from ..presentation.renderers.quota_markdown import ( - render_quota_markdown, - render_quota_scheduler_ack_markdown, - render_quota_scheduler_failure_markdown, - render_quota_should_run_markdown, -) - -from ..presentation.renderers.turn_envelope_markdown import ( - render_turn_envelope_markdown, -) def _quota_renderer( args: argparse.Namespace, *, failure: bool = False, ) -> Callable[[dict[str, object]], str]: + # Renderers and compaction load only when a route prints a quota view; + # native context delivery owns its own presentation. + from ..presentation.renderers.quota_event_markdown import ( + render_quota_monitor_poll_markdown, + render_quota_slot_preview_markdown, + ) + from ..presentation.renderers.quota_markdown import ( + render_quota_markdown, + render_quota_scheduler_ack_markdown, + render_quota_scheduler_failure_markdown, + render_quota_should_run_markdown, + ) + from ..presentation.renderers.turn_envelope_markdown import ( + render_turn_envelope_markdown, + ) + command = args.quota_command if not failure and bool(getattr(args, "turn_envelope", False)): return render_turn_envelope_markdown @@ -61,6 +57,11 @@ def _project_quota_cli_payload( A rejected envelope likewise retains the already-decided facts (issue #3687). """ from ..control_plane.effect_program import reject_retired_quota_format + from ..control_plane.quota.cli_projection import ( + compact_quota_monitor_poll_cli_payload, + compact_quota_plan_cli_payload, + compact_quota_should_run_cli_payload, + ) reject_retired_quota_format(payload) if payload.get("ok") is False and not isinstance(payload.get("interaction_contract"), Mapping): diff --git a/loopx/cli_commands/quota_reward_memory.py b/loopx/cli_commands/quota_reward_memory.py index ba593af7d6..b4d2147c62 100644 --- a/loopx/cli_commands/quota_reward_memory.py +++ b/loopx/cli_commands/quota_reward_memory.py @@ -6,13 +6,6 @@ from pathlib import Path from typing import Any -from ..capabilities.turn_context import attach_turn_context -from ..capabilities.reward_memory.codex_app_outcome import ( - stage_codex_app_turn_outcome_candidate_fail_open, -) -from ..capabilities.reward_memory.turn_hooks import run_staged_memory_settlement -from ..control_plane.quota.settlement import read_heartbeat_settlement - def stage_reward_memory_outcome_candidate( payload: dict[str, Any], @@ -27,6 +20,9 @@ def stage_reward_memory_outcome_candidate( validation_workspace: Path, ) -> None: """Attach a privately staged outcome candidate after a valid refresh.""" + from ..capabilities.reward_memory.codex_app_outcome import ( + stage_codex_app_turn_outcome_candidate_fail_open, + ) settlement_identity = payload.get("settlement_identity") settlement_identity = ( @@ -78,6 +74,9 @@ def attach_reward_memory_ingest_after_spend( ) -> None: if not execute or payload.get("ok") is not True: return + from ..capabilities.reward_memory.turn_hooks import run_staged_memory_settlement + from ..control_plane.quota.settlement import read_heartbeat_settlement + readback = read_heartbeat_settlement( runtime_root, goal_id=goal_id, @@ -137,6 +136,8 @@ def attach_reward_memory_recall_after_should_run( or heartbeat_receipt.get("status") not in {"committed", "replayed"} ): return + from ..capabilities.turn_context import attach_turn_context + attach_turn_context( payload, registry_path=registry_path, diff --git a/tests/test_cli_entrypoint.py b/tests/test_cli_entrypoint.py index a011c354a8..89ba10e3e8 100644 --- a/tests/test_cli_entrypoint.py +++ b/tests/test_cli_entrypoint.py @@ -1,5 +1,6 @@ from __future__ import annotations +import hashlib import json import os import shutil @@ -418,6 +419,105 @@ def test_quota_facade_lazy_exports_preserve_owner_identity() -> None: assert completed.returncode == 0, completed.stderr +QUOTA_AUTHORITY_AND_COMPOSITION_OWNERS = ( + "loopx.status", + "loopx.quota", + "loopx.upgrade", + "loopx.cli_commands.lark_inbox", + "loopx.cli_commands.quota_context", + "loopx.control_plane.quota.live_decision", + "loopx.control_plane.quota.cli_projection", + "loopx.capabilities.explore", + "loopx.capabilities.periodic_report", + "loopx.capabilities.repository_change_window", + "loopx.capabilities.reward_memory", + "loopx.capabilities.turn_context", +) +# Interaction hooks, the live decision, host observation, and recall exist only +# for should-run; accounting routes must not compose them. +QUOTA_SHOULD_RUN_ONLY_OWNERS = ( + "loopx.cli", + "loopx.upgrade", + "loopx.control_plane.quota.live_decision", + "loopx.capabilities.periodic_report.pending_intent", + "loopx.capabilities.repository_change_window", + "loopx.capabilities.turn_context", +) + + +def test_quota_handler_import_defers_owners_and_preserves_export_identity() -> None: + script = f""" +from importlib import import_module +import sys +import loopx.cli_commands.quota as command + +for owner in {QUOTA_AUTHORITY_AND_COMPOSITION_OWNERS!r}: + assert owner not in sys.modules, owner +for name, (module, attribute) in command._LAZY_EXPORTS.items(): + assert name in dir(command), name + value = getattr(command, name) + assert value is getattr(import_module(module), attribute), name + assert getattr(command, name) is value, name +try: + command.nonexistent_quota_handler_export +except AttributeError: + pass +else: + raise AssertionError("unknown quota handler exports must fail") +""" + completed = run_isolated_script(script) + assert completed.returncode == 0, completed.stderr + + +@pytest.mark.parametrize("route", ["context-read", "replan-view"]) +def test_quota_pre_authority_routes_skip_authority_owners(tmp_path: Path, route: str) -> None: + registry, runtime = write_command_fixture(tmp_path) + capture = tmp_path / "decision.json" + capture.write_text("{\"ok\": true, \"goal_id\": \"perf-goal\"}\n", encoding="utf-8") + digest = hashlib.sha256(capture.read_bytes()).hexdigest() + arguments = { + "context-read": ["--capture-file", str(capture), "--capture-sha256", digest], + "replan-view": ["--decision-file", str(tmp_path / "missing.json"), + "--outcome-ref", "outcome", "--source-hash", digest], + }[route] + before = registry.read_bytes() + argv = ["--registry", str(registry), "--runtime-root", str(runtime), "--format", "json", + "quota", route, *arguments] + selected = run_cli_main("loopx.entrypoint", argv, + forbidden_modules=("loopx.cli", *QUOTA_AUTHORITY_AND_COMPOSITION_OWNERS)) + full = run_cli_main("loopx.cli", argv) + assert selected.returncode == full.returncode == (0 if route == "context-read" else 1) + assert selected.stdout == full.stdout + assert selected.stderr == full.stderr + assert registry.read_bytes() == before + assert not runtime.exists() + + +def test_quota_accounting_routes_skip_should_run_composition(tmp_path, monkeypatch) -> None: + from tests.control_plane.canonical_authority_fixture import initialize_canonical_source_fixture, isolate_sqlite_runtime + + isolate_sqlite_runtime(tmp_path, monkeypatch) + registry, runtime = write_command_fixture(tmp_path) + state = registry.parent.parent / ".codex/goals/perf-goal/ACTIVE_GOAL_STATE.md" + initialize_canonical_source_fixture(runtime, "perf-goal", state_path=state, provider="file") + base = ["--registry", str(registry), "--runtime-root", str(runtime), "--format", "json", "quota"] + identity = ["--goal-id", "perf-goal", "--agent-id", "perf-agent"] + cases = [ + (["spend-slot", "--slots", "1", "--source", "heartbeat", "--todo-id", "todo_cli_dispatch"], 0), + (["void-slot", "--void-generated-at", "2026-08-28T00:00:00+00:00", + "--reason-summary", "Checked fixture"], 1), + (["monitor-poll"], 1), + ] + for arguments, expected_code in cases: + result = run_cli_main("loopx.entrypoint", [*base, *arguments, *identity], + forbidden_modules=QUOTA_SHOULD_RUN_ONLY_OWNERS) + assert result.returncode == expected_code, result.stderr + payload = json.loads(result.stdout) + assert payload["mode"] == arguments[0] + assert payload["ok"] is (expected_code == 0) + assert payload.get("appended") is not True + + def test_selected_parser_matches_full_help_and_diagnostics() -> None: argv_cases = [ *[[command, flag] for command in