From a5b742f198a6758d4aeb04f90efabbb5066a5444 Mon Sep 17 00:00:00 2001 From: "lusendong.6789" Date: Wed, 4 Nov 2026 01:29:53 +0800 Subject: [PATCH] fix(quota): unify private capture platform qualification Render the owner-generated settlement budget only when the private capture reader can actually run. Reuse one host-qualification helper for command generation and capture execution so POSIX-looking hosts without getuid also receive the full-result command. Signed-off-by: huangruiteng --- loopx/control_plane/quota/effect_program.py | 3 +- .../turn_driver/decision_capture.py | 10 +++++-- tests/control_plane/test_quota_settlement.py | 28 +++++++++++++++---- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/loopx/control_plane/quota/effect_program.py b/loopx/control_plane/quota/effect_program.py index ba363a775b..c99a27974d 100644 --- a/loopx/control_plane/quota/effect_program.py +++ b/loopx/control_plane/quota/effect_program.py @@ -30,6 +30,7 @@ from ..goals.goal_ref_validation import exact_goal_ref from ..work_items.delivery_batch_scale import DELIVERY_BATCH_SCALE_INPUT_CHOICES from ..turn_driver.native_context_profile import NATIVE_CONTEXT_PAGE_OUTPUT_BYTES +from ..turn_driver.decision_capture import private_capture_supported _PLATFORM_IS_POSIX = os.name == "posix" @@ -269,7 +270,7 @@ def writeback_command(boundary: str) -> str: ) max_output_arg = ( f" --max-page-output-bytes {NATIVE_CONTEXT_PAGE_OUTPUT_BYTES}" - if _PLATFORM_IS_POSIX + if private_capture_supported() else "" ) spend = ( diff --git a/loopx/control_plane/turn_driver/decision_capture.py b/loopx/control_plane/turn_driver/decision_capture.py index 94cd30a70d..cc226ca3c5 100644 --- a/loopx/control_plane/turn_driver/decision_capture.py +++ b/loopx/control_plane/turn_driver/decision_capture.py @@ -26,17 +26,21 @@ ) +def private_capture_supported() -> bool: + """Whether this host has the POSIX identity needed for private capture IO.""" + return os.name == "posix" and callable(getattr(os, "getuid", None)) + + def private_capture_uid() -> int: """Fail closed until this IO boundary has a qualified non-POSIX ACL reader. POSIX mode bits cannot establish Windows privacy. Check before creating or reading private captures, and before evaluating bounded settlement commands. """ - getuid = getattr(os, "getuid", None) - if os.name != "posix" or not callable(getuid): + if not private_capture_supported(): raise ValueError("private capture validation is unsupported on this platform; " "use the same Turn command without --max-page-output-bytes") - return getuid() + return getattr(os, "getuid")() def write_decision_capture( diff --git a/tests/control_plane/test_quota_settlement.py b/tests/control_plane/test_quota_settlement.py index ed70156e50..1f8d8277d4 100644 --- a/tests/control_plane/test_quota_settlement.py +++ b/tests/control_plane/test_quota_settlement.py @@ -46,6 +46,7 @@ SchedulerRuntimeProfile, scheduler_execution_context_for_runtime_profile, ) +from loopx.control_plane.turn_driver import decision_capture as decision_capture_module from loopx.control_plane.work_items.interaction_contract import ( build_interaction_contract, interaction_next_cli_actions, @@ -805,10 +806,27 @@ def quote(value: object) -> str: assert '"${LOOPX_TURN:?}"' not in command -def test_windows_settlement_command_omits_unsupported_max_page_output_bytes() -> None: - # Non-POSIX platforms fail closed on private capture validation; settlement - # spend recipes must omit --max-page-output-bytes on Windows. - with patch.object(quota_effect_program, "_PLATFORM_IS_POSIX", False): +@pytest.mark.parametrize(("platform", "supported"), [ + (SimpleNamespace(name="nt", getuid=lambda: 0), False), + (SimpleNamespace(name="posix", getuid=None), False), + (SimpleNamespace(name="posix", getuid=lambda: 0), True), +]) +def test_private_capture_support_matches_uid_boundary( + platform: SimpleNamespace, supported: bool, monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr(decision_capture_module, "os", platform) + assert decision_capture_module.private_capture_supported() is supported + if supported: + assert decision_capture_module.private_capture_uid() == 0 + else: + with pytest.raises(ValueError, match="unsupported on this platform"): + decision_capture_module.private_capture_uid() + + +def test_settlement_command_uses_private_capture_support() -> None: + # The generated command and the CLI's private-capture check must share one + # platform qualification; a POSIX-looking host without getuid is unsupported. + with patch.object(quota_effect_program, "private_capture_supported", return_value=False): plan = quota_effect_program.build_turn_scoped_cli_settlement_plan( goal_id=GOAL_ID, agent_id=AGENT_ID, todo_id=TODO_ID, turn_instance_id=TURN_ID, scoped_cli_args="", lifecycle_actor_args="", @@ -817,7 +835,7 @@ def test_windows_settlement_command_omits_unsupported_max_page_output_bytes() -> assert command is not None assert "--max-page-output-bytes" not in command - with patch.object(quota_effect_program, "_PLATFORM_IS_POSIX", True): + with patch.object(quota_effect_program, "private_capture_supported", return_value=True): plan = quota_effect_program.build_turn_scoped_cli_settlement_plan( goal_id=GOAL_ID, agent_id=AGENT_ID, todo_id=TODO_ID, turn_instance_id=TURN_ID, scoped_cli_args="", lifecycle_actor_args="",