Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion loopx/control_plane/quota/effect_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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 = (
Expand Down
10 changes: 7 additions & 3 deletions loopx/control_plane/turn_driver/decision_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
28 changes: 23 additions & 5 deletions tests/control_plane/test_quota_settlement.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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="",
Expand All @@ -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="",
Expand Down
Loading