From 0212a1f2ebb8f60649a7df3bab957501976e78a7 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:24:29 +0800 Subject: [PATCH] fix(quota): preserve original facts in context sharing Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- loopx/control_plane/quota/cli_projection.py | 3 +- loopx/control_plane/quota/turn_envelope.ts | 4 +- .../test_quota_context_reference_owner.py | 55 ++++++++++++++++++- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/loopx/control_plane/quota/cli_projection.py b/loopx/control_plane/quota/cli_projection.py index 45ef36f048..335e9abd4e 100644 --- a/loopx/control_plane/quota/cli_projection.py +++ b/loopx/control_plane/quota/cli_projection.py @@ -558,8 +558,7 @@ def _reference_context( result = _turn_envelope_result("reference_context", payload=payload, task_title=task_title, explore_guidance=explore_guidance, **({"diagnostics": diagnostics} if diagnostics is not None else {})) - # Existing reference-only callers keep their historical wire contract. - return result if diagnostics is None else _apply_context_projection_changes(payload, result) + return _apply_context_projection_changes(payload, result) def _apply_context_projection_changes( diff --git a/loopx/control_plane/quota/turn_envelope.ts b/loopx/control_plane/quota/turn_envelope.ts index 214c8506fc..0a442e86b4 100644 --- a/loopx/control_plane/quota/turn_envelope.ts +++ b/loopx/control_plane/quota/turn_envelope.ts @@ -1465,7 +1465,9 @@ export function evaluateTurnEnvelope(value: unknown): JsonObject { if (jsonObject(request.diagnostics)) payload = projectQuotaDiagnostics(payload, request.diagnostics as JsonObject); if (request.task_title === true) payload = referenceCurrentTaskTitle(payload); if (request.explore_guidance === true) payload = referenceExploreGuidance(payload); - return jsonObject(request.diagnostics) ? contextProjectionChanges(source, payload) : payload; + // References and diagnostic caps share one edit transport. Untouched facts + // stay in the original caller, including numbers JSON.parse cannot retain. + return contextProjectionChanges(source, payload); } if (operation === "build") return buildTurnEnvelope(request); if (operation === "context_delivery") return prepareContextDelivery(request, LEGACY_QUOTA_CONTEXT_FIELDS); diff --git a/tests/control_plane/test_quota_context_reference_owner.py b/tests/control_plane/test_quota_context_reference_owner.py index 76ef472719..e4b009d359 100644 --- a/tests/control_plane/test_quota_context_reference_owner.py +++ b/tests/control_plane/test_quota_context_reference_owner.py @@ -1,6 +1,5 @@ """Shared context references preserve full instructions across existing entrypoints.""" from copy import deepcopy -import hashlib import json import pytest @@ -52,10 +51,15 @@ def test_shared_guidance_reaches_existing_readers_without_changing_settlement(ca if captured: projected = _turn_envelope_result("replan_cli_view", payload=shared) assert graph(projected) == graph(source) - digest = "sha256:" + hashlib.sha256(json.dumps(shared, ensure_ascii=False, - sort_keys=True, separators=(",", ":")).encode()).hexdigest() + # Use the existing source signature, also embedded in captured read + # commands. Python's numeric spelling is not its canonical hash codec. + digest = envelope["action_signature"]["source_decision_hash"] writer = _turn_envelope_result("replan_view", payload=shared, source_hash=digest, stage="writeback") assert writer["writeback"]["settlement_plan"] == source["interaction_contract"]["cli_channel"]["settlement_plan"] + changed = deepcopy(shared) + changed["reason"] += " Mutated capture." + with pytest.raises(RuntimeError, match="matching source hash"): + _turn_envelope_result("replan_view", payload=changed, source_hash=digest, stage="writeback") @pytest.mark.parametrize("mutation", ["different", "wrong-step", "wrong-capability", "missing-owner", "short", "wrong-source"]) @@ -193,3 +197,48 @@ def unavailable(*args, **kwargs): for source in ({"ok": False, "diagnostic": "unavailable"}, {"interaction_contract": {"agent_channel": {"work_context": {"sources": []}}}}): assert _reference_context(source) is source + + +@pytest.mark.parametrize("value", [1.0, -0.0, 2**60 + 1, float(2**60), 0.000001]) +@pytest.mark.parametrize("entrypoint", ["reference", "diagnostics", "cli-detail"]) +def test_context_sharing_keeps_original_numeric_facts(value, entrypoint): + from loopx.control_plane.quota.cli_projection import compact_quota_should_run_cli_payload + source, _ = packet() + facts = {"future_measurement": value, "nested": [value, False, None]} + source["future_facts"] = deepcopy(facts) + attachment(source)["future_facts"] = deepcopy(facts) + original = json.dumps(source) + + def project(payload): + if entrypoint == "cli-detail": + return compact_quota_should_run_cli_payload(payload, + include_todo_summary_detail=True, include_user_todo_summary_detail=True) + return _reference_context(payload, **({"diagnostics": {}} if entrypoint == "diagnostics" else {})) + + shared = project(source) + # JSON spelling distinguishes float/int, negative zero and adjacent large + # integers; Python equality alone cannot establish that facts survived. + assert json.dumps(shared["future_facts"]) == json.dumps(facts) + assert json.dumps(attachment(shared)["future_facts"]) == json.dumps(facts) + assert graph(shared)["result_writeback_guidance_ref"].endswith("optional_attachments[0].guidance") + assert json.dumps(project(shared)) == json.dumps(shared) + assert json.dumps(source) == original + + +def test_reference_transport_does_not_return_untouched_material(monkeypatch): + from loopx.control_plane.quota import turn_envelope + source, _ = packet() + body = "Untouched supporting material. " * 2048 + source["future_facts"] = {"body": body} + responses, real = [], turn_envelope._turn_envelope_result + + def observed(operation, **params): + result = real(operation, **params) + responses.append(json.dumps(result)) + return result + + monkeypatch.setattr(turn_envelope, "_turn_envelope_result", observed) + shared = _reference_context(source) + assert shared["future_facts"]["body"] == body + assert len(responses) == 1 + assert body not in responses[0]