Skip to content

Commit 71c497e

Browse files
authored
Merge branch 'main' into codex/acp-config-option-model
2 parents 5fec2c0 + 1dcd65f commit 71c497e

2 files changed

Lines changed: 181 additions & 1 deletion

File tree

.github/scripts/check_agent_server_rest_api_breakage.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,33 @@ def _validate_removed_schema_properties(
574574
_EXTENSIBLE_DISCRIMINATOR_PROPERTY_RE = re.compile(
575575
r"HookConfig\b.*\bhooks/items/type\b"
576576
)
577+
_ACCEPTED_CLOUD_PROXY_PATH_REMOVAL_ID = "api-path-removed-without-deprecation"
578+
_ACCEPTED_CLOUD_PROXY_REMOVAL_PATH = "/api/cloud-proxy"
579+
_ACCEPTED_CLOUD_PROXY_REMOVAL_METHOD = "post"
580+
_ACCEPTED_CLOUD_PROXY_REMOVAL_OPERATION_ID = "cloud_proxy_api_cloud_proxy_post"
581+
582+
583+
def _is_accepted_cloud_proxy_removal(operation: dict) -> bool:
584+
"""Return True for the accepted /api/cloud-proxy removal from PR #3326."""
585+
path = str(operation.get("path", ""))
586+
method = str(operation.get("method", "")).lower()
587+
return (
588+
path == _ACCEPTED_CLOUD_PROXY_REMOVAL_PATH
589+
and method == _ACCEPTED_CLOUD_PROXY_REMOVAL_METHOD
590+
and operation.get("deprecated", False) is False
591+
)
592+
593+
594+
def _is_accepted_cloud_proxy_path_removal(change: dict) -> bool:
595+
"""Return True for oasdiff's accepted /api/cloud-proxy path-removal shape."""
596+
return (
597+
str(change.get("id", "")) == _ACCEPTED_CLOUD_PROXY_PATH_REMOVAL_ID
598+
and str(change.get("path", "")) == _ACCEPTED_CLOUD_PROXY_REMOVAL_PATH
599+
and str(change.get("operation", "")).lower()
600+
== _ACCEPTED_CLOUD_PROXY_REMOVAL_METHOD
601+
and str(change.get("operationId", ""))
602+
== _ACCEPTED_CLOUD_PROXY_REMOVAL_OPERATION_ID
603+
)
577604

578605

579606
def _is_additive_discriminator_enum_value(change: dict) -> bool:
@@ -815,6 +842,26 @@ def main() -> int:
815842
for change in other_breaking_changes
816843
if not _is_union_type_change_artifact(change)
817844
]
845+
accepted_cloud_proxy_removals = [
846+
operation
847+
for operation in removed_operations
848+
if _is_accepted_cloud_proxy_removal(operation)
849+
]
850+
removed_operations = [
851+
operation
852+
for operation in removed_operations
853+
if not _is_accepted_cloud_proxy_removal(operation)
854+
]
855+
accepted_cloud_proxy_path_removals = [
856+
change
857+
for change in other_breaking_changes
858+
if _is_accepted_cloud_proxy_path_removal(change)
859+
]
860+
other_breaking_changes = [
861+
change
862+
for change in other_breaking_changes
863+
if not _is_accepted_cloud_proxy_path_removal(change)
864+
]
818865

819866
removal_errors = _validate_removed_operations(
820867
removed_operations,
@@ -830,6 +877,14 @@ def main() -> int:
830877
for error in removal_errors + property_removal_errors:
831878
print(f"::error title={PYPI_DISTRIBUTION} REST API::{error}")
832879

880+
if accepted_cloud_proxy_removals or accepted_cloud_proxy_path_removals:
881+
print(
882+
f"\n::notice title={PYPI_DISTRIBUTION} REST API::"
883+
"Accepted removal of POST /api/cloud-proxy. Maintainers "
884+
"explicitly accepted this REST break in PR #3326, and that PR "
885+
"is labeled release-note-required."
886+
)
887+
833888
if additive_response_oneof:
834889
print(
835890
f"\n::notice title={PYPI_DISTRIBUTION} REST API::"
@@ -881,7 +936,8 @@ def main() -> int:
881936
print(
882937
"Breaking changes are limited to previously-deprecated operations "
883938
"or properties whose scheduled removal versions have been reached, "
884-
"and/or additive response oneOf expansions."
939+
"the accepted POST /api/cloud-proxy removal, and/or additive "
940+
"response oneOf expansions."
885941
)
886942
else:
887943
return 1

tests/cross/test_check_agent_server_rest_api_breakage.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,56 @@ def test_rest_deprecation_regex_matches_deprecation_check_regex():
239239
assert _rest_route_deprecation_re.flags == _deprecation_check_re.flags
240240

241241

242+
def test_accepted_cloud_proxy_removal_detection_is_exact():
243+
assert _prod._is_accepted_cloud_proxy_removal(
244+
{"path": "/api/cloud-proxy", "method": "post", "deprecated": False}
245+
)
246+
assert not _prod._is_accepted_cloud_proxy_removal(
247+
{"path": "/api/cloud-proxy", "method": "get", "deprecated": False}
248+
)
249+
assert not _prod._is_accepted_cloud_proxy_removal(
250+
{"path": "/api/cloud-proxy/other", "method": "post", "deprecated": False}
251+
)
252+
assert not _prod._is_accepted_cloud_proxy_removal(
253+
{"path": "/api/cloud-proxy", "method": "post", "deprecated": True}
254+
)
255+
256+
257+
def test_accepted_cloud_proxy_path_removal_detection_is_exact():
258+
assert _prod._is_accepted_cloud_proxy_path_removal(
259+
{
260+
"id": "api-path-removed-without-deprecation",
261+
"path": "/api/cloud-proxy",
262+
"operation": "POST",
263+
"operationId": "cloud_proxy_api_cloud_proxy_post",
264+
}
265+
)
266+
assert not _prod._is_accepted_cloud_proxy_path_removal(
267+
{
268+
"id": "api-path-removed-without-deprecation",
269+
"path": "/api/cloud-proxy",
270+
"operation": "GET",
271+
"operationId": "cloud_proxy_api_cloud_proxy_post",
272+
}
273+
)
274+
assert not _prod._is_accepted_cloud_proxy_path_removal(
275+
{
276+
"id": "api-path-removed-without-deprecation",
277+
"path": "/api/cloud-proxy/other",
278+
"operation": "POST",
279+
"operationId": "cloud_proxy_api_cloud_proxy_post",
280+
}
281+
)
282+
assert not _prod._is_accepted_cloud_proxy_path_removal(
283+
{
284+
"id": "api-path-removed-without-deprecation",
285+
"path": "/api/cloud-proxy",
286+
"operation": "POST",
287+
"operationId": "other_operation",
288+
}
289+
)
290+
291+
242292
def test_parse_openapi_deprecation_description_extracts_versions_from_example():
243293
description = (
244294
"Nice description here with more context for API consumers.\n\n"
@@ -418,6 +468,80 @@ def test_validate_removed_schema_properties_requires_removal_target_to_be_reache
418468
]
419469

420470

471+
def test_main_allows_accepted_cloud_proxy_removal(monkeypatch, capsys):
472+
monkeypatch.setattr(_prod, "_read_version_from_pyproject", lambda _path: "1.28.0")
473+
monkeypatch.setattr(
474+
_prod, "_get_baseline_version", lambda _distribution, _current: "1.28.0"
475+
)
476+
monkeypatch.setattr(_prod, "_find_sdk_deprecated_fastapi_routes", lambda _root: [])
477+
monkeypatch.setattr(_prod, "_generate_current_openapi", lambda: {"paths": {}})
478+
monkeypatch.setattr(_prod, "_find_deprecation_policy_errors", lambda _schema: [])
479+
monkeypatch.setattr(
480+
_prod, "_generate_openapi_for_git_ref", lambda _ref: {"paths": {}}
481+
)
482+
monkeypatch.setattr(_prod, "_normalize_openapi_for_oasdiff", lambda schema: schema)
483+
monkeypatch.setattr(
484+
_prod,
485+
"_run_oasdiff_breakage_check",
486+
lambda _prev, _cur: (
487+
[
488+
{
489+
"id": "removed-operation",
490+
"details": {
491+
"path": "/api/cloud-proxy",
492+
"method": "post",
493+
"deprecated": False,
494+
},
495+
"text": "removed POST /api/cloud-proxy",
496+
}
497+
],
498+
1,
499+
),
500+
)
501+
502+
assert _prod.main() == 0
503+
504+
captured = capsys.readouterr()
505+
assert "Accepted removal of POST /api/cloud-proxy" in captured.out
506+
assert "accepted POST /api/cloud-proxy removal" in captured.out
507+
508+
509+
def test_main_allows_accepted_cloud_proxy_path_removal(monkeypatch, capsys):
510+
monkeypatch.setattr(_prod, "_read_version_from_pyproject", lambda _path: "1.28.0")
511+
monkeypatch.setattr(
512+
_prod, "_get_baseline_version", lambda _distribution, _current: "1.28.0"
513+
)
514+
monkeypatch.setattr(_prod, "_find_sdk_deprecated_fastapi_routes", lambda _root: [])
515+
monkeypatch.setattr(_prod, "_generate_current_openapi", lambda: {"paths": {}})
516+
monkeypatch.setattr(_prod, "_find_deprecation_policy_errors", lambda _schema: [])
517+
monkeypatch.setattr(
518+
_prod, "_generate_openapi_for_git_ref", lambda _ref: {"paths": {}}
519+
)
520+
monkeypatch.setattr(_prod, "_normalize_openapi_for_oasdiff", lambda schema: schema)
521+
monkeypatch.setattr(
522+
_prod,
523+
"_run_oasdiff_breakage_check",
524+
lambda _prev, _cur: (
525+
[
526+
{
527+
"id": "api-path-removed-without-deprecation",
528+
"text": "api path removed without deprecation",
529+
"operation": "POST",
530+
"operationId": "cloud_proxy_api_cloud_proxy_post",
531+
"path": "/api/cloud-proxy",
532+
}
533+
],
534+
1,
535+
),
536+
)
537+
538+
assert _prod.main() == 0
539+
540+
captured = capsys.readouterr()
541+
assert "Accepted removal of POST /api/cloud-proxy" in captured.out
542+
assert "accepted POST /api/cloud-proxy removal" in captured.out
543+
544+
421545
def test_main_allows_scheduled_removal_with_documented_target(monkeypatch, capsys):
422546
prev_schema = _schema_with_operation(
423547
"/api/foo",

0 commit comments

Comments
 (0)