diff --git a/docs/reference/contracts/interface-budget-contract.md b/docs/reference/contracts/interface-budget-contract.md index 2484f1bb86..91beb0e4c1 100644 --- a/docs/reference/contracts/interface-budget-contract.md +++ b/docs/reference/contracts/interface-budget-contract.md @@ -618,6 +618,23 @@ Windows 原生路径资格检查把 brief JSON 上限调至 10,800,Markdown 同时保留已测 523 字符内容增量和路径转义余量。这是两项预算的组合,不能作为 新的 Windows 原生实测;原始路径测量、行数和语义检查仍保留其各自边界。 +Complete executable routes also render the owned fixture root once per action. +The same 36-Todo / 12-run crowded quota JSON emits 39,810 characters from the +short calibration root and 40,860 from a 58-character owned root (31 serialized +characters per root character). The hosted Windows runner owns a 53-character +root and measured 40,806, above the previous 40,000 ceiling. The crowded JSON +ceiling is now 41,200; complete routes, line, growth and semantic limits stay +unchanged. This is a measured host-path allowance, not a new semantic payload, +and a focused regression extrapolates the serialized cost from two owned root +lengths and requires the 53-character native root to fit. + +完整可执行路径会在每个 action 中渲染一次夹具根目录。同一 36 Todo/12 run 的 +crowded quota JSON 在短校准根目录下为 39,810 字符,在 58 字符根目录下为 40,860, +即每个根字符约 31 个序列化字符。托管 Windows runner 的根目录为 53 字符,实测 +40,806,超过原 40,000 上限。crowded JSON 上限调整为 41,200,完整路径、行数、 +增长和语义限制不变。这是实测的主机路径余量而非新的语义载荷;聚焦回归用两个根目录 +长度外推序列化成本,并要求 53 字符原生根目录落在上限内。 + The frozen 128-character Windows-spelling fixture was also compared on main `b5a1a816a` and the rebased outcome candidate without changing its routes. The body grows from 5,556 to 6,079 characters and Markdown from 8,579 to 9,102: diff --git a/loopx/control_plane/testing/cli_output_budget.py b/loopx/control_plane/testing/cli_output_budget.py index e353bfe1c8..c874ca8610 100644 --- a/loopx/control_plane/testing/cli_output_budget.py +++ b/loopx/control_plane/testing/cli_output_budget.py @@ -151,7 +151,11 @@ class CliOutputCommandClassification: # Per-Todo and fixed semantic growth are accounted for separately. # Canonical source witnesses use the full Markdown context carrier; # the unchanged 36-Todo fixture emits 10,225 chars / 166 lines. - "crowded": {"json": 40_000, "markdown": 10_500}, + # Complete executable routes render the owned root once per action: + # the short calibration root emits 39,810 JSON chars, while the + # 53-character hosted Windows runner root emits 40,806 (31 chars per + # root character). Keep the full routes and cover that native layout. + "crowded": {"json": 41_200, "markdown": 10_500}, # Matched canonical base/head both emit 24,820 JSON characters. "multi_agent": {"json": 25_500, "markdown": 7_000}, }, diff --git a/tests/control_plane/test_cli_output_budget.py b/tests/control_plane/test_cli_output_budget.py index e928757102..67e45ad79c 100644 --- a/tests/control_plane/test_cli_output_budget.py +++ b/tests/control_plane/test_cli_output_budget.py @@ -1816,6 +1816,60 @@ def test_long_path_budget_is_independent_of_runner_temp_prefix( test_brief_budget_retains_full_commands_on_real_long_paths() +def _crowded_quota_json_measurement(root: Path) -> tuple[int, dict]: + with _stable_budget_fixture_root(root) as stable_root: + project, runtime, registry_path, state_file = _write_fixture( + stable_root, SCENARIOS[1], + ) + import_fixture_authority(registry_path, GOAL_ID) + command = _surface_commands( + project=project, + runtime=runtime, + registry_path=registry_path, + state_file=state_file, + output_format="json", + )["quota_should_run"] + exit_code, text = _invoke_cli(command) + assert exit_code == 0, text + return len(str(budget_fixture_path(root))), measure_cli_output( + text, output_format="json", + ) + + +def test_crowded_quota_budget_covers_the_hosted_runner_root( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, +) -> None: + # Complete executable routes render the owned root once per route. The + # hosted Windows runner owns a 53-character root under %TEMP%; a longer + # ambient root is therefore already a stronger layout than the runner. + # Measure the serialization cost per root character here instead of + # depending on the host's temporary namespace. + ambient_root, ambient = _crowded_quota_json_measurement(tmp_path / "ambient") + long_parent = tmp_path / ("nested-crowded-quota-" + "p" * 8) + long_parent.mkdir() + monkeypatch.setattr(tempfile, "tempdir", str(long_parent)) + long_root, longer = _crowded_quota_json_measurement(tmp_path / "longer") + assert long_root > ambient_root + per_root_character = (longer["chars"] - ambient["chars"]) / ( + long_root - ambient_root + ) + assert per_root_character > 0 + hosted_runner_root = 53 + native_chars = ambient["chars"] + round( + per_root_character * (hosted_runner_root - ambient_root) + ) + assert native_chars <= CLI_OUTPUT_BUDGET_BY_ID["quota_should_run"].max_chars[ + "crowded" + ]["json"] + # The raise covers host-path rendering only: at the 24-character + # calibration root the same payload still fits the original 40,000 bound. + calibration_root = 24 + semantic_chars = ambient["chars"] + round( + per_root_character * (calibration_root - ambient_root) + ) + assert semantic_chars <= 40_000 + + @pytest.mark.parametrize("nested_temp", [False, True]) def test_long_path_budget_uses_native_temp_without_a_drive_root_directory( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, nested_temp: bool,