diff --git a/gateway/gateway.py b/gateway/gateway.py index 8ce6b8bbdf..0a9d9fa384 100644 --- a/gateway/gateway.py +++ b/gateway/gateway.py @@ -1253,9 +1253,7 @@ def git_execute() -> tuple[Response, int] | Response: # Pipeline agents in worktrees must stay on their assigned branch. # Interactive sessions are unrestricted even if they use worktrees. # We detect pipeline sessions by the presence of pipeline_id on the - # session (set for both "issue" and "local" pipeline modes), rather - # than checking session_mode, because issue-mode pipelines use - # session_mode="public" while local-mode pipelines use "local". + # session, rather than checking session_mode. # See issue #773. session = getattr(g, "session", None) is_pipeline = session is not None and getattr(session, "pipeline_id", None) is not None @@ -2147,21 +2145,6 @@ def gh_pr_create() -> tuple[Response, int] | Response: # Get session phase from request context (set by @require_session_auth decorator) session_phase = getattr(g, "session_phase", None) - # Block PR creation in local SDLC mode (except during PR phase, where - # phase-permissions grant it and the gateway provides push access). - if session_mode == "local" and session_phase != "pr": - audit_log( - "pr_create_blocked_local_mode", - "gh_pr_create", - success=False, - details={"repo": repo, "reason": "PR creation blocked in local SDLC mode"}, - ) - return make_error( - "Operation blocked in local SDLC mode. Create PR manually when the pipeline completes.", - status_code=403, - details={"session_mode": "local"}, - ) - # Check phase restrictions (if session has a phase set) if session_phase: try: @@ -2355,21 +2338,6 @@ def gh_pr_comment() -> tuple[Response, int] | Response: # Get session mode from request context (set by @require_session_auth decorator) session_mode = getattr(g, "session_mode", None) - # Block PR comment in local SDLC mode (except during PR phase) - session_phase = getattr(g, "session_phase", None) - if session_mode == "local" and session_phase != "pr": - audit_log( - "pr_comment_blocked_local_mode", - "gh_pr_comment", - success=False, - details={"repo": repo, "reason": "PR comment blocked in local SDLC mode"}, - ) - return make_error( - "Operation blocked in local SDLC mode. Interact with PRs manually when the pipeline completes.", - status_code=403, - details={"session_mode": "local"}, - ) - # Check Private Repo Mode policy (if enabled) repo_info = parse_owner_repo(repo) if repo_info: @@ -2495,21 +2463,6 @@ def gh_pr_edit() -> tuple[Response, int] | Response: # Get session mode from request context (set by @require_session_auth decorator) session_mode = getattr(g, "session_mode", None) - # Block PR edit in local SDLC mode (except during PR phase) - session_phase = getattr(g, "session_phase", None) - if session_mode == "local" and session_phase != "pr": - audit_log( - "pr_edit_blocked_local_mode", - "gh_pr_edit", - success=False, - details={"repo": repo, "reason": "PR edit blocked in local SDLC mode"}, - ) - return make_error( - "Operation blocked in local SDLC mode. Edit PRs manually when the pipeline completes.", - status_code=403, - details={"session_mode": "local"}, - ) - # Check Private Repo Mode policy (if enabled) priv_result = check_private_repo_access( operation="pr_edit", @@ -2616,21 +2569,6 @@ def gh_pr_close() -> tuple[Response, int] | Response: # Get session mode from request context (set by @require_session_auth decorator) session_mode = getattr(g, "session_mode", None) - # Block PR close in local SDLC mode (except during PR phase) - session_phase = getattr(g, "session_phase", None) - if session_mode == "local" and session_phase != "pr": - audit_log( - "pr_close_blocked_local_mode", - "gh_pr_close", - success=False, - details={"repo": repo, "reason": "PR close blocked in local SDLC mode"}, - ) - return make_error( - "Operation blocked in local SDLC mode. Close PRs manually when the pipeline completes.", - status_code=403, - details={"session_mode": "local"}, - ) - # Check Private Repo Mode policy (if enabled) repo_info = parse_owner_repo(repo) if repo_info: @@ -2734,40 +2672,6 @@ def gh_execute() -> tuple[Response, int] | Response: # Get session mode from request context (set by @require_session_auth decorator) session_mode = getattr(g, "session_mode", None) - # Block gh commands in local SDLC mode. - # During PR phase, only allow PR-scoped operations through. - # All other gh commands remain blocked. - session_phase = getattr(g, "session_phase", None) - if session_mode == "local": - allowed = False - if session_phase == "pr": - cmd_prefix = " ".join(args[:2]) if len(args) >= 2 else args[0] if args else "" - allowed_pr_phase_prefixes = ( - "pr create", - "pr edit", - "pr view", - "pr list", - "pr comment", - "pr close", - "pr diff", - "pr checks", - "pr status", - ) - allowed = any(cmd_prefix.startswith(p) for p in allowed_pr_phase_prefixes) - - if not allowed: - audit_log( - "gh_command_blocked_local_mode", - "gh_execute", - success=False, - details={"command_args": args, "reason": "gh commands blocked in local SDLC mode"}, - ) - return make_error( - "Operation blocked in local SDLC mode. Run gh commands manually when the pipeline completes.", - status_code=403, - details={"session_mode": "local"}, - ) - # Check for commands blocked entirely in private mode (too broad to filter by repo) if session_mode == "private" and args and args[0] in GH_COMMANDS_BLOCKED_IN_PRIVATE_MODE: audit_log( @@ -3467,11 +3371,10 @@ def session_create() -> tuple[Response, int] | Response: return make_error("Missing container_id") if not container_ip: return make_error("Missing container_ip") - if mode not in ("private", "public", "local"): - return make_error("Invalid mode: must be 'private', 'public', or 'local'") - # repos is required for private/public modes unless local_only_repos are provided. - # local mode (orchestrator-internal temp sessions) needs no repos at all. - if not repos and not local_only_repos and mode != "local": + if mode not in ("private", "public"): + return make_error("Invalid mode: must be 'private' or 'public'") + # repos can be omitted for orchestrator-internal sessions that have a pipeline_id + if not repos and not local_only_repos and not pipeline_id: return make_error("Missing repos list") # Validate uid/gid if provided diff --git a/gateway/session_manager.py b/gateway/session_manager.py index 209e8a9bc9..5d6ea7d84e 100644 --- a/gateway/session_manager.py +++ b/gateway/session_manager.py @@ -229,7 +229,7 @@ def _capture_and_cleanup_session( SESSION_PERSISTENCE_FILE = SESSION_PERSISTENCE_DIR / "sessions.json" # Mode type alias -ModeType = Literal["private", "public", "local"] +ModeType = Literal["private", "public"] def _hash_token(token: str) -> str: diff --git a/gateway/tests/test_gateway.py b/gateway/tests/test_gateway.py index 8c88392b58..de2ea11031 100644 --- a/gateway/tests/test_gateway.py +++ b/gateway/tests/test_gateway.py @@ -3724,46 +3724,8 @@ def test_session_create_rejects_empty_pipeline_id(self, client, launcher_auth_he assert "pipeline_id" in data["message"].lower() assert "non-empty" in data["message"].lower() - def test_session_create_local_mode_without_repos(self, client, launcher_auth_headers, tmp_path): - """Local-mode session succeeds without repos. - - Orchestrator-internal temp sessions (push_worktree_branch, fetch_branch, - etc.) use mode="local" without repos — they only need a session token for - git authentication, not worktree creation or visibility filtering. - """ - from session_manager import SessionManager - - manager = SessionManager(persistence_file=tmp_path / "sessions.json") - - with patch.object(gateway, "get_session_manager", return_value=manager): - response = client.post( - "/api/v1/sessions/create", - headers=launcher_auth_headers, - data=json.dumps( - { - "container_id": "test-container", - "container_ip": "172.18.0.5", - "mode": "local", - } - ), - content_type="application/json", - ) - - assert response.status_code == 200 - data = json.loads(response.data) - assert data["success"] is True - assert "session_token" in data["data"] - - # Verify session was registered and is usable - token = data["data"]["session_token"] - session = manager.get_session(token) - assert session is not None - assert session.mode == "local" - - def test_session_create_non_local_mode_still_requires_repos( - self, client, launcher_auth_headers - ): - """Private/public modes still reject missing repos.""" + def test_session_create_without_repos_rejected(self, client, launcher_auth_headers): + """Sessions without repos are rejected.""" for mode in ("private", "public"): response = client.post( "/api/v1/sessions/create", @@ -4100,472 +4062,6 @@ def test_worktree_create_uses_explicit_base_branch(self, client, launcher_auth_h assert base == "origin/develop" -class TestLocalModeBlocking: - """Tests for local SDLC mode blocking across PR endpoints and gh_execute. - - Verifies that: - - Endpoints return 403 when session_mode='local' and phase is not 'pr' - - Endpoints allow requests when session_mode='local' and phase='pr' - - gh_execute only allows PR-related commands in local + PR phase - """ - - @pytest.fixture - def local_mode_headers(self): - """Return session headers with local mode and configurable phase.""" - import sys - - import auth - - def _make_headers(phase: str | None): - mock_session = MagicMock() - mock_session.mode = "local" - mock_session.container_id = "test-container" - mock_session.expires_at = None - mock_session.phase = phase - - mock_result = SessionValidationResult(valid=True, session=mock_session) - - from private_repo_policy import PrivateRepoPolicyResult - - mock_policy_result = PrivateRepoPolicyResult( - allowed=True, - reason="Test mode - access allowed", - visibility="public", - ) - - auth._session_manager = None - auth._rate_limiter = None - - if "gateway.auth" in sys.modules: - sys.modules["gateway.auth"]._session_manager = None - sys.modules["gateway.auth"]._rate_limiter = None - - current_session_manager = sys.modules.get("session_manager", session_manager) - - return ( - {"Authorization": "Bearer test-session-token"}, - mock_result, - mock_policy_result, - current_session_manager, - ) - - return _make_headers - - # --- Blocking tests: phase is not 'pr' --- - - def test_pr_create_blocked_local_mode_implement_phase(self, client, local_mode_headers): - """PR create returns 403 in local mode when phase is 'implement'.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers( - "implement" - ) - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - ): - response = client.post( - "/api/v1/gh/pr/create", - headers=headers, - data=json.dumps({"repo": "test/repo", "title": "Test PR", "head": "feature"}), - content_type="application/json", - ) - - assert response.status_code == 403 - data = json.loads(response.data) - assert "local" in data["message"].lower() - - def test_pr_comment_blocked_local_mode_no_phase(self, client, local_mode_headers): - """PR comment returns 403 in local mode when phase is None.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers(None) - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - ): - response = client.post( - "/api/v1/gh/pr/comment", - headers=headers, - data=json.dumps({"repo": "test/repo", "pr_number": 1, "body": "comment"}), - content_type="application/json", - ) - - assert response.status_code == 403 - data = json.loads(response.data) - assert "local" in data["message"].lower() - - def test_pr_edit_blocked_local_mode_implement_phase(self, client, local_mode_headers): - """PR edit returns 403 in local mode when phase is 'implement'.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers( - "implement" - ) - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - ): - response = client.post( - "/api/v1/gh/pr/edit", - headers=headers, - data=json.dumps({"repo": "test/repo", "pr_number": 1, "title": "New Title"}), - content_type="application/json", - ) - - assert response.status_code == 403 - data = json.loads(response.data) - assert "local" in data["message"].lower() - - def test_pr_close_blocked_local_mode_implement_phase(self, client, local_mode_headers): - """PR close returns 403 in local mode when phase is 'implement'.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers( - "implement" - ) - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - ): - response = client.post( - "/api/v1/gh/pr/close", - headers=headers, - data=json.dumps({"repo": "test/repo", "pr_number": 1}), - content_type="application/json", - ) - - assert response.status_code == 403 - data = json.loads(response.data) - assert "local" in data["message"].lower() - - def test_gh_execute_blocked_local_mode_no_phase(self, client, local_mode_headers): - """gh execute returns 403 in local mode when phase is None.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers(None) - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - ): - response = client.post( - "/api/v1/gh/execute", - headers=headers, - data=json.dumps({"args": ["pr", "list"]}), - content_type="application/json", - ) - - assert response.status_code == 403 - data = json.loads(response.data) - assert "local" in data["message"].lower() - - def test_gh_execute_blocked_local_mode_implement_phase(self, client, local_mode_headers): - """gh execute returns 403 in local mode when phase is 'implement'.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers( - "implement" - ) - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - ): - response = client.post( - "/api/v1/gh/execute", - headers=headers, - data=json.dumps({"args": ["issue", "list"]}), - content_type="application/json", - ) - - assert response.status_code == 403 - - # --- Allow tests: phase is 'pr' --- - - def test_pr_create_allowed_local_mode_pr_phase(self, client, local_mode_headers): - """PR create succeeds in local mode when phase is 'pr'.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers("pr") - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - patch.object(gateway, "get_github_client") as mock_gh, - ): - mock_gh_result = MagicMock() - mock_gh_result.success = True - mock_gh_result.stdout = "https://github.com/test/repo/pull/1" - mock_gh_result.stderr = "" - mock_gh_result.to_dict.return_value = { - "success": True, - "stdout": "https://github.com/test/repo/pull/1", - "stderr": "", - } - mock_gh.return_value.execute.return_value = mock_gh_result - - response = client.post( - "/api/v1/gh/pr/create", - headers=headers, - data=json.dumps({"repo": "test/repo", "title": "Test PR", "head": "feature"}), - content_type="application/json", - ) - - assert response.status_code == 200 - - def test_pr_comment_allowed_local_mode_pr_phase(self, client, local_mode_headers): - """PR comment succeeds in local mode when phase is 'pr'.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers("pr") - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - patch.object(gateway, "get_policy_engine") as mock_policy, - patch.object(gateway, "get_github_client") as mock_gh, - ): - mock_engine = MagicMock() - mock_engine.check_pr_ownership.return_value = PolicyResult( - allowed=True, - reason="PR is owned by bot", - details={"author": "bot"}, - ) - mock_policy.return_value = mock_engine - - mock_gh_result = MagicMock() - mock_gh_result.success = True - mock_gh_result.stdout = "comment posted" - mock_gh_result.stderr = "" - mock_gh.return_value.execute.return_value = mock_gh_result - - response = client.post( - "/api/v1/gh/pr/comment", - headers=headers, - data=json.dumps({"repo": "test/repo", "pr_number": 1, "body": "LGTM"}), - content_type="application/json", - ) - - assert response.status_code == 200 - - def test_pr_edit_allowed_local_mode_pr_phase(self, client, local_mode_headers): - """PR edit succeeds in local mode when phase is 'pr'.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers("pr") - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - patch.object(gateway, "get_policy_engine") as mock_policy, - patch.object(gateway, "get_github_client") as mock_gh, - ): - mock_engine = MagicMock() - mock_engine.check_pr_ownership.return_value = PolicyResult( - allowed=True, - reason="PR is owned by bot", - details={"author": "bot"}, - ) - mock_policy.return_value = mock_engine - - mock_gh_result = MagicMock() - mock_gh_result.success = True - mock_gh_result.stdout = "PR updated" - mock_gh_result.stderr = "" - mock_gh.return_value.execute.return_value = mock_gh_result - - response = client.post( - "/api/v1/gh/pr/edit", - headers=headers, - data=json.dumps({"repo": "test/repo", "pr_number": 1, "title": "Updated"}), - content_type="application/json", - ) - - assert response.status_code == 200 - - def test_pr_close_allowed_local_mode_pr_phase(self, client, local_mode_headers): - """PR close succeeds in local mode when phase is 'pr'.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers("pr") - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - patch.object(gateway, "get_policy_engine") as mock_policy, - patch.object(gateway, "get_github_client") as mock_gh, - ): - mock_engine = MagicMock() - mock_engine.check_pr_ownership.return_value = PolicyResult( - allowed=True, - reason="PR is owned by bot", - details={"author": "bot"}, - ) - mock_policy.return_value = mock_engine - - mock_gh_result = MagicMock() - mock_gh_result.success = True - mock_gh_result.stdout = "PR closed" - mock_gh_result.stderr = "" - mock_gh.return_value.execute.return_value = mock_gh_result - - response = client.post( - "/api/v1/gh/pr/close", - headers=headers, - data=json.dumps({"repo": "test/repo", "pr_number": 1}), - content_type="application/json", - ) - - assert response.status_code == 200 - - # --- gh_execute scope tests: only PR commands allowed in local + PR phase --- - - def test_gh_execute_allows_pr_list_local_pr_phase(self, client, local_mode_headers): - """gh execute allows 'pr list' in local mode during PR phase.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers("pr") - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - patch.object(gateway, "get_github_client") as mock_gh, - ): - mock_gh_result = MagicMock() - mock_gh_result.success = True - mock_gh_result.stdout = "PR #1: Feature" - mock_gh_result.stderr = "" - mock_gh_result.to_dict.return_value = { - "success": True, - "stdout": "PR #1: Feature", - "stderr": "", - } - mock_gh.return_value.execute.return_value = mock_gh_result - - response = client.post( - "/api/v1/gh/execute", - headers=headers, - data=json.dumps({"args": ["pr", "list"]}), - content_type="application/json", - ) - - assert response.status_code == 200 - - def test_gh_execute_allows_pr_view_local_pr_phase(self, client, local_mode_headers): - """gh execute allows 'pr view' in local mode during PR phase.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers("pr") - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - patch.object(gateway, "get_github_client") as mock_gh, - ): - mock_gh_result = MagicMock() - mock_gh_result.success = True - mock_gh_result.stdout = "PR details" - mock_gh_result.stderr = "" - mock_gh_result.to_dict.return_value = { - "success": True, - "stdout": "PR details", - "stderr": "", - } - mock_gh.return_value.execute.return_value = mock_gh_result - - response = client.post( - "/api/v1/gh/execute", - headers=headers, - data=json.dumps({"args": ["pr", "view", "123"]}), - content_type="application/json", - ) - - assert response.status_code == 200 - - def test_gh_execute_blocks_issue_edit_local_pr_phase(self, client, local_mode_headers): - """gh execute blocks 'issue edit' in local mode during PR phase.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers("pr") - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - ): - response = client.post( - "/api/v1/gh/execute", - headers=headers, - data=json.dumps({"args": ["issue", "edit", "1", "--title", "new"]}), - content_type="application/json", - ) - - assert response.status_code == 403 - data = json.loads(response.data) - assert "local" in data["message"].lower() - - def test_gh_execute_blocks_release_create_local_pr_phase(self, client, local_mode_headers): - """gh execute blocks 'release create' in local mode during PR phase.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers("pr") - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - ): - response = client.post( - "/api/v1/gh/execute", - headers=headers, - data=json.dumps({"args": ["release", "create", "v1.0"]}), - content_type="application/json", - ) - - assert response.status_code == 403 - - def test_gh_execute_blocks_repo_edit_local_pr_phase(self, client, local_mode_headers): - """gh execute blocks 'repo edit' in local mode during PR phase.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers("pr") - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - ): - response = client.post( - "/api/v1/gh/execute", - headers=headers, - data=json.dumps({"args": ["repo", "edit", "--description", "new"]}), - content_type="application/json", - ) - - assert response.status_code == 403 - - def test_gh_execute_blocks_api_command_local_pr_phase(self, client, local_mode_headers): - """gh execute blocks 'api' command in local mode during PR phase.""" - headers, mock_result, mock_policy_result, current_session_manager = local_mode_headers("pr") - - with ( - patch.object( - current_session_manager, "validate_session_for_request", return_value=mock_result - ), - patch.object(gateway, "check_private_repo_access", return_value=mock_policy_result), - ): - response = client.post( - "/api/v1/gh/execute", - headers=headers, - data=json.dumps({"args": ["api", "repos/test/repo/issues"]}), - content_type="application/json", - ) - - assert response.status_code == 403 - - class TestSessionDeleteByContainerWorktreeCleanup: """Tests for DELETE /api/v1/sessions/by-container/ worktree cleanup.""" @@ -4728,21 +4224,21 @@ class TestBranchIsolation: """Tests for branch isolation enforcement in pipeline worktree sessions. Branch isolation applies to all pipeline sessions (identified by pipeline_id), - regardless of session_mode. Both "local" and "issue" pipeline modes are covered. + regardless of session_mode. Pipeline sessions (with pipeline_id) are restricted. Interactive sessions (no pipeline_id) are unrestricted even with worktrees. File operations (checkout -- , restore) are always allowed. See issue #773. """ @pytest.fixture - def local_mode_headers(self): - """Return session headers with local (pipeline) mode for branch isolation tests.""" + def pipeline_mode_headers(self): + """Return session headers with pipeline mode for branch isolation tests.""" import sys import auth mock_session = MagicMock() - mock_session.mode = "local" + mock_session.mode = "public" mock_session.container_id = "test-container" mock_session.expires_at = None mock_session.phase = "implement" @@ -4791,21 +4287,21 @@ def _git_execute(self, client, headers, operation, args=None, container_id="test content_type="application/json", ) - def test_checkout_branch_blocked_in_pipeline_worktree(self, client, local_mode_headers): - """git checkout should be blocked in pipeline (local mode) worktree.""" + def test_checkout_branch_blocked_in_pipeline_worktree(self, client, pipeline_mode_headers): + """git checkout should be blocked in pipeline worktree.""" with patch.object( gateway, "map_container_path_to_worktree", return_value="/home/egg/.egg-worktrees/test-container/test", ): - response = self._git_execute(client, local_mode_headers, "checkout", ["main"]) + response = self._git_execute(client, pipeline_mode_headers, "checkout", ["main"]) assert response.status_code == 403 data = json.loads(response.data) assert "branch switching" in data["message"].lower() assert "pipeline" in data["message"].lower() - def test_checkout_b_blocked_in_pipeline_worktree(self, client, local_mode_headers): + def test_checkout_b_blocked_in_pipeline_worktree(self, client, pipeline_mode_headers): """git checkout -b should be blocked in pipeline worktree.""" with patch.object( gateway, @@ -4813,12 +4309,12 @@ def test_checkout_b_blocked_in_pipeline_worktree(self, client, local_mode_header return_value="/home/egg/.egg-worktrees/test-container/test", ): response = self._git_execute( - client, local_mode_headers, "checkout", ["-b", "new-branch", "origin/main"] + client, pipeline_mode_headers, "checkout", ["-b", "new-branch", "origin/main"] ) assert response.status_code == 403 - def test_checkout_file_allowed_in_worktree(self, client, local_mode_headers): + def test_checkout_file_allowed_in_worktree(self, client, pipeline_mode_headers): """git checkout -- should be allowed even in pipeline worktree.""" with ( patch.object( @@ -4830,7 +4326,9 @@ def test_checkout_file_allowed_in_worktree(self, client, local_mode_headers): ): mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") - response = self._git_execute(client, local_mode_headers, "checkout", ["--", "file.txt"]) + response = self._git_execute( + client, pipeline_mode_headers, "checkout", ["--", "file.txt"] + ) assert response.status_code == 200 assert mock_run.called, "Expected subprocess.run to be called for allowed checkout" @@ -4839,7 +4337,7 @@ def test_checkout_file_allowed_in_worktree(self, client, local_mode_headers): assert "--" in cmd assert "file.txt" in cmd - def test_checkout_ours_allowed_in_worktree(self, client, local_mode_headers): + def test_checkout_ours_allowed_in_worktree(self, client, pipeline_mode_headers): """git checkout --ours should be allowed even in pipeline worktree.""" with ( patch.object( @@ -4852,7 +4350,7 @@ def test_checkout_ours_allowed_in_worktree(self, client, local_mode_headers): mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") response = self._git_execute( - client, local_mode_headers, "checkout", ["--ours", "file.txt"] + client, pipeline_mode_headers, "checkout", ["--ours", "file.txt"] ) assert response.status_code == 200 @@ -4861,20 +4359,20 @@ def test_checkout_ours_allowed_in_worktree(self, client, local_mode_headers): assert "checkout" in cmd assert "--ours" in cmd - def test_switch_blocked_in_pipeline_worktree(self, client, local_mode_headers): + def test_switch_blocked_in_pipeline_worktree(self, client, pipeline_mode_headers): """git switch should be blocked in pipeline worktree.""" with patch.object( gateway, "map_container_path_to_worktree", return_value="/home/egg/.egg-worktrees/test-container/test", ): - response = self._git_execute(client, local_mode_headers, "switch", ["main"]) + response = self._git_execute(client, pipeline_mode_headers, "switch", ["main"]) assert response.status_code == 403 data = json.loads(response.data) assert "branch switching" in data["message"].lower() - def test_switch_create_blocked_in_pipeline_worktree(self, client, local_mode_headers): + def test_switch_create_blocked_in_pipeline_worktree(self, client, pipeline_mode_headers): """git switch --create should be blocked in pipeline worktree.""" with patch.object( gateway, @@ -4882,12 +4380,12 @@ def test_switch_create_blocked_in_pipeline_worktree(self, client, local_mode_hea return_value="/home/egg/.egg-worktrees/test-container/test", ): response = self._git_execute( - client, local_mode_headers, "switch", ["--create", "new-branch"] + client, pipeline_mode_headers, "switch", ["--create", "new-branch"] ) assert response.status_code == 403 - def test_checkout_allowed_without_worktree(self, client, local_mode_headers): + def test_checkout_allowed_without_worktree(self, client, pipeline_mode_headers): """git checkout should be allowed without a worktree even in pipeline mode.""" with ( patch.object( @@ -4900,7 +4398,7 @@ def test_checkout_allowed_without_worktree(self, client, local_mode_headers): mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") response = self._git_execute( - client, local_mode_headers, "checkout", ["-b", "new-branch"] + client, pipeline_mode_headers, "checkout", ["-b", "new-branch"] ) assert response.status_code == 200 @@ -5056,7 +4554,7 @@ def test_checkout_blocked_in_issue_mode_pipeline_worktree(self, client): assert "branch switching" in data["message"].lower() assert "pipeline" in data["message"].lower() - def test_restore_allowed_in_worktree(self, client, local_mode_headers): + def test_restore_allowed_in_worktree(self, client, pipeline_mode_headers): """git restore should be allowed in pipeline worktree (file-restore alternative).""" with ( patch.object( @@ -5069,7 +4567,7 @@ def test_restore_allowed_in_worktree(self, client, local_mode_headers): mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") response = self._git_execute( - client, local_mode_headers, "restore", ["--staged", "file.txt"] + client, pipeline_mode_headers, "restore", ["--staged", "file.txt"] ) assert response.status_code == 200 @@ -5078,7 +4576,7 @@ def test_restore_allowed_in_worktree(self, client, local_mode_headers): assert "restore" in cmd assert "--staged" in cmd - def test_status_allowed_in_worktree(self, client, local_mode_headers): + def test_status_allowed_in_worktree(self, client, pipeline_mode_headers): """Non-branch-switching operations should be allowed in pipeline worktree.""" with ( patch.object( @@ -5090,7 +4588,7 @@ def test_status_allowed_in_worktree(self, client, local_mode_headers): ): mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="") - response = self._git_execute(client, local_mode_headers, "status", ["--porcelain"]) + response = self._git_execute(client, pipeline_mode_headers, "status", ["--porcelain"]) assert response.status_code == 200 assert mock_run.called, "Expected subprocess.run to be called for allowed status" diff --git a/integration_tests/local_pipeline/helpers.py b/integration_tests/local_pipeline/helpers.py index 533991ef18..392a59e1bf 100644 --- a/integration_tests/local_pipeline/helpers.py +++ b/integration_tests/local_pipeline/helpers.py @@ -12,7 +12,6 @@ def create_pipeline( orchestrator_url: str, *, - mode: str = "local", prompt: str = "Test pipeline", issue_number: int | None = None, repo: str | None = None, @@ -20,7 +19,7 @@ def create_pipeline( config: dict | None = None, ) -> tuple[dict, int]: """Create a pipeline via the orchestrator API.""" - body: dict = {"mode": mode, "prompt": prompt} + body: dict = {"prompt": prompt} if issue_number is not None: body["issue_number"] = issue_number if repo is not None: diff --git a/integration_tests/local_pipeline/test_api_validation.py b/integration_tests/local_pipeline/test_api_validation.py index 259261cda3..7ffe432b7f 100644 --- a/integration_tests/local_pipeline/test_api_validation.py +++ b/integration_tests/local_pipeline/test_api_validation.py @@ -19,32 +19,6 @@ # --------------------------------------------------------------------------- -class TestInvalidMode: - """Test POST /pipelines with invalid mode.""" - - def test_invalid_mode_returns_400(self, orchestrator_url: str) -> None: - """Returns 400 with clear error message about valid modes.""" - resp = requests.post( - f"{orchestrator_url}/api/v1/pipelines", - json={ - "mode": "invalid_mode_xyz", - "prompt": "Test with invalid mode", - }, - timeout=10, - ) - - assert resp.status_code == 400, ( - f"Expected 400 for invalid mode, got {resp.status_code}: {resp.text}" - ) - - data = resp.json() - # Error message should indicate the issue - error_msg = data.get("message", "") or data.get("error", "") or str(data) - assert "mode" in error_msg.lower() or "invalid" in error_msg.lower(), ( - f"Error should mention invalid mode: {data}" - ) - - class TestMissingRequiredFields: """Test POST /pipelines with missing required fields.""" @@ -53,7 +27,6 @@ def test_missing_prompt_returns_400(self, orchestrator_url: str) -> None: resp = requests.post( f"{orchestrator_url}/api/v1/pipelines", json={ - "mode": "local", # Missing "prompt" field }, timeout=10, @@ -84,21 +57,21 @@ def test_empty_body_returns_400(self, orchestrator_url: str) -> None: f"Expected 400 for empty body, got {resp.status_code}: {resp.text}" ) - def test_issue_mode_missing_issue_number_returns_400(self, orchestrator_url: str) -> None: - """Returns 400 when issue mode is used without issue_number.""" + def test_issue_number_requires_repo_returns_400(self, orchestrator_url: str) -> None: + """Returns 400 when issue_number is used without repo.""" resp = requests.post( f"{orchestrator_url}/api/v1/pipelines", json={ - "mode": "issue", - "prompt": "Test without issue number", - # Missing issue_number for issue mode + "prompt": "Test with issue but no repo", + "issue_number": 123, + # Missing repo for issue pipeline }, timeout=10, ) - # Should return 400 for missing issue_number in issue mode + # Should return 400 for missing repo with issue_number assert resp.status_code == 400, ( - f"Expected 400 for issue mode without issue_number, got {resp.status_code}" + f"Expected 400 for issue_number without repo, got {resp.status_code}" ) diff --git a/integration_tests/local_pipeline/test_error_recovery.py b/integration_tests/local_pipeline/test_error_recovery.py index f02470f2b2..aca947d72e 100644 --- a/integration_tests/local_pipeline/test_error_recovery.py +++ b/integration_tests/local_pipeline/test_error_recovery.py @@ -276,7 +276,7 @@ def test_no_orphaned_containers_after_failure(self, orchestrator_url: str) -> No # Filter to containers related to this pipeline pipeline_containers = [ - c for c in after_containers if pipeline_id.replace("local-", "") in c + c for c in after_containers if pipeline_id.replace("pipeline-", "") in c ] assert len(pipeline_containers) == 0, ( @@ -337,7 +337,7 @@ def test_delete_running_pipeline_stops_container(self, orchestrator_url: str) -> time.sleep(3) containers = get_orphaned_sandbox_containers() pipeline_containers = [ - c for c in containers if pipeline_id.replace("local-", "") in c + c for c in containers if pipeline_id.replace("pipeline-", "") in c ] assert len(pipeline_containers) == 0, ( f"Found orphaned containers: {pipeline_containers}" diff --git a/integration_tests/local_pipeline/test_local_pipeline.py b/integration_tests/local_pipeline/test_local_pipeline.py index 46929a749b..264d737b90 100644 --- a/integration_tests/local_pipeline/test_local_pipeline.py +++ b/integration_tests/local_pipeline/test_local_pipeline.py @@ -9,7 +9,6 @@ import random import subprocess -import time import pytest import requests @@ -67,10 +66,9 @@ def test_create_local_pipeline(self, orchestrator_url: str) -> None: assert status == 200, f"Unexpected status {status}: {data}" assert data["success"] is True pipeline = data["data"]["pipeline"] - assert pipeline["mode"] == "local" assert pipeline["prompt"] == "Add a logout button to the navbar" assert pipeline["status"] == "pending" - assert pipeline["id"].startswith("local-") + assert pipeline["id"].startswith("pipeline-") assert pipeline["current_phase"] == "refine" # Cleanup @@ -109,7 +107,6 @@ def test_start_local_pipeline_completes(self, orchestrator_url: str) -> None: assert get_status == 200 pipeline = get_data["data"]["pipeline"] assert pipeline["status"] == "complete" - assert pipeline["mode"] == "local" finally: delete_pipeline(orchestrator_url, pipeline_id) @@ -427,7 +424,6 @@ def test_issue_pipeline_includes_pr_phase(self, orchestrator_url: str) -> None: issue_num = random.randint(10000, 99999) data, status = create_pipeline( orchestrator_url, - mode="issue", prompt="Fix the login bug", issue_number=issue_num, repo="test-owner/test-repo", @@ -486,7 +482,6 @@ def test_pipeline_crud(self, orchestrator_url: str) -> None: assert get_status == 200 pipeline = get_data["data"]["pipeline"] assert pipeline["id"] == pipeline_id - assert pipeline["mode"] == "local" assert pipeline["prompt"] == "CRUD test pipeline" assert pipeline["status"] == "pending" @@ -520,81 +515,6 @@ def test_pipeline_crud(self, orchestrator_url: str) -> None: raise -class TestGatewayLocalModeBlocksPush: - """Register a local session, attempt push -> 403.""" - - def test_gateway_local_mode_blocks_push(self, local_pipeline_stack) -> None: - """Create a local-mode gateway session and verify push is blocked. - - The gateway validates sessions by matching request.remote_addr against - the session's container_ip. Since we're sending requests from the test - host through a mapped port, we first detect what IP the gateway sees - for our requests (via the health endpoint), then bind the session to - that IP so session validation passes and the local-mode push block - (which happens AFTER session auth) can be tested. - """ - gateway_url = local_pipeline_stack.gateway_url - launcher_secret = local_pipeline_stack.launcher_secret - - # Detect the source IP the gateway sees for requests from this host - health_resp = requests.get( - f"{gateway_url}/api/v1/health", - timeout=10, - ) - source_ip = health_resp.json().get("client_ip", "") - assert source_ip, "Gateway health endpoint did not return client_ip" - - # Create a local-mode session bound to our actual source IP - session_resp = requests.post( - f"{gateway_url}/api/v1/sessions/create", - headers={"Authorization": f"Bearer {launcher_secret}"}, - json={ - "container_id": f"test-push-block-{int(time.time())}", - "container_ip": source_ip, - "mode": "local", - "repos": ["test-owner/test-repo"], - "uid": 1000, - "gid": 1000, - }, - timeout=10, - ) - session_data = session_resp.json() - assert session_data.get("success") is True, f"Session creation failed: {session_data}" - - session_token = session_data.get("data", session_data).get("session_token") - assert session_token, f"No session token in response: {session_data}" - - try: - # Attempt a push — should be blocked with 403 - push_resp = requests.post( - f"{gateway_url}/api/v1/git/push", - headers={"Authorization": f"Bearer {session_token}"}, - json={ - "repo_path": "/home/egg/repos", - "refspec": "egg/test-branch", - }, - timeout=10, - ) - - assert push_resp.status_code == 403, ( - f"Expected 403 for local mode push, got {push_resp.status_code}: {push_resp.text}" - ) - - push_data = push_resp.json() - assert ( - "local" in push_data.get("message", "").lower() - or "local" in str(push_data.get("details", {})).lower() - ), f"Error message should mention local mode: {push_data}" - - finally: - # Cleanup session - requests.delete( - f"{gateway_url}/api/v1/sessions/{session_token}", - headers={"Authorization": f"Bearer {launcher_secret}"}, - timeout=10, - ) - - class TestReviewCycleApproved: """Multiple reviewer containers are spawned for each reviewed phase. diff --git a/integration_tests/local_pipeline/test_unified_pipeline_behavior.py b/integration_tests/local_pipeline/test_unified_pipeline_behavior.py index 8c1fc06862..161a0987df 100644 --- a/integration_tests/local_pipeline/test_unified_pipeline_behavior.py +++ b/integration_tests/local_pipeline/test_unified_pipeline_behavior.py @@ -1,13 +1,12 @@ -"""Integration tests for unified local/issue pipeline behavior (issue #543). +"""Integration tests for unified pipeline behavior (issue #543). -Tests verify that local and issue mode pipelines now follow the same -contract/checkpoint/push discipline with the only difference being -where initial context comes from. +Tests verify that pipelines follow the same contract/checkpoint/push +discipline regardless of how they were created. Key behaviors tested: -1. Phase-based push restrictions (not blanket mode-based blocking) +1. Phase-based push restrictions 2. Contract creation with pipeline_id key -3. Checkpoint creation on local-mode pushes +3. Checkpoint creation on pushes 4. Prefixed file paths for concurrent pipeline support 5. Container and agent tracking via API 6. State persistence at phase boundaries @@ -39,7 +38,7 @@ def test_contract_file_uses_pipeline_id_key(self, local_pipeline_stack) -> None: ) assert status == 200 pipeline_id = data["data"]["pipeline"]["id"] - assert pipeline_id.startswith("local-") + assert pipeline_id.startswith("pipeline-") try: # Verify contract file path uses pipeline_id @@ -135,14 +134,14 @@ def test_gateway_session_in_local_refine_allows_state_push(self, local_pipeline_ health_resp = requests.get(f"{gateway_url}/api/v1/health", timeout=10) source_ip = health_resp.json().get("client_ip", "") - # Create a local-mode session in refine phase + # Create a session in refine phase session_resp = requests.post( f"{gateway_url}/api/v1/sessions/create", headers={"Authorization": f"Bearer {launcher_secret}"}, json={ "container_id": f"test-push-{int(time.time())}", "container_ip": source_ip, - "mode": "local", + "mode": "public", "phase": "refine", # Key: setting phase enables per-phase restrictions "repos": ["test-owner/test-repo"], "uid": 1000, diff --git a/integration_tests/local_pipeline/test_worktree_integration.py b/integration_tests/local_pipeline/test_worktree_integration.py index d1185f1e29..25a4d606b6 100644 --- a/integration_tests/local_pipeline/test_worktree_integration.py +++ b/integration_tests/local_pipeline/test_worktree_integration.py @@ -469,12 +469,11 @@ def test_worktree_deletion_cleans_up_properly( def create_pipeline( orchestrator_url: str, *, - mode: str = "local", prompt: str = "Test pipeline", config: dict | None = None, ) -> tuple[dict, int]: """Create a pipeline via the orchestrator API.""" - body: dict = {"mode": mode, "prompt": prompt} + body: dict = {"prompt": prompt} if config is not None: body["config"] = config resp = requests.post( @@ -778,7 +777,7 @@ def test_orphaned_worktree_cleanup_on_session_delete( json={ "container_id": container_id, "container_ip": source_ip, - "mode": "local", + "mode": "public", "repos": ["test-owner/test-repo"], "uid": 1000, "gid": 1000, diff --git a/orchestrator/container_spawner.py b/orchestrator/container_spawner.py index d174e88662..1846763b7a 100644 --- a/orchestrator/container_spawner.py +++ b/orchestrator/container_spawner.py @@ -158,7 +158,7 @@ def _build_network_config(self, mode: str) -> ContainerNetworkConfig: """Build ContainerNetworkConfig for the given gateway mode. Args: - mode: Gateway mode (public, private, or local) + mode: Gateway mode (public or private) Returns: ContainerNetworkConfig with correct network, IPs, and repo_mode. @@ -171,15 +171,6 @@ def _build_network_config(self, mode: str) -> ContainerNetworkConfig: gateway_port=GATEWAY_PORT, repo_mode="private", ) - elif mode == "local": - # Local mode: isolated network but no proxy/DNS lockdown - return ContainerNetworkConfig( - network_name=EGG_ISOLATED_NETWORK, - gateway_hostname=GATEWAY_CONTAINER_NAME, - gateway_ip=GATEWAY_ISOLATED_IP, - gateway_port=GATEWAY_PORT, - repo_mode="public", - ) else: # "public" return ContainerNetworkConfig( network_name=EGG_EXTERNAL_NETWORK, diff --git a/orchestrator/gateway_client.py b/orchestrator/gateway_client.py index 499432c080..366d648130 100644 --- a/orchestrator/gateway_client.py +++ b/orchestrator/gateway_client.py @@ -620,7 +620,7 @@ def push_worktree_branch( session = self.register_session( container_id=temp_container_id, container_ip=self.self_ip, - mode="local", + mode="public", pipeline_id=pipeline_id, ) session_token = session.session_token @@ -686,7 +686,7 @@ def delete_remote_branch( session = self.register_session( container_id=temp_container_id, container_ip=self.self_ip, - mode="local", + mode="public", pipeline_id=pipeline_id, ) session_token = session.session_token @@ -763,7 +763,7 @@ def create_pr( session = self.register_session( container_id=temp_container_id, container_ip=self.self_ip, - mode="local", + mode="public", pipeline_id=pipeline_id, phase="pr", repos=[repo], @@ -829,7 +829,7 @@ def fetch_worktree_branch( session = self.register_session( container_id=temp_container_id, container_ip=self.self_ip, - mode="local", + mode="public", pipeline_id=pipeline_id, ) session_token = session.session_token @@ -888,7 +888,7 @@ def fetch_branch( session = self.register_session( container_id=temp_container_id, container_ip=self.self_ip, - mode="local", + mode="public", pipeline_id=pipeline_id, ) session_token = session.session_token @@ -948,7 +948,7 @@ def ls_remote_branch( session = self.register_session( container_id=temp_container_id, container_ip=self.self_ip, - mode="local", + mode="public", pipeline_id=pipeline_id, ) session_token = session.session_token diff --git a/orchestrator/mcp_tools.py b/orchestrator/mcp_tools.py index b5c6d3d941..058c54f775 100644 --- a/orchestrator/mcp_tools.py +++ b/orchestrator/mcp_tools.py @@ -196,10 +196,8 @@ def _handle_submit_task(self, args: dict[str, Any]) -> dict[str, Any]: } if args.get("issue_number"): data["issue_number"] = args["issue_number"] - data["mode"] = "issue" data["branch"] = args.get("branch") or f"egg/issue-{args['issue_number']}" else: - data["mode"] = "local" data["prompt"] = args["description"] if args.get("repo"): data["repo"] = args["repo"] diff --git a/orchestrator/models.py b/orchestrator/models.py index a552f54fb1..154c03f5b4 100644 --- a/orchestrator/models.py +++ b/orchestrator/models.py @@ -383,12 +383,13 @@ class Pipeline(BaseModel): It tracks all state needed to orchestrate a pipeline from issue to PR. """ - id: str = Field(..., description="Unique pipeline ID (e.g., 'issue-496' or 'local-a1b2c3d4')") + id: str = Field( + ..., description="Unique pipeline ID (e.g., 'issue-496' or 'pipeline-a1b2c3d4')" + ) issue_number: int | None = Field(default=None, ge=1, description="GitHub issue number") repo: str | None = Field(default=None, description="Repository in owner/name format") branch: str | None = Field(default=None, description="Work branch name") - mode: str = Field(default="issue", description="Pipeline mode: 'issue' or 'local'") - prompt: str | None = Field(default=None, description="User prompt for local-mode pipelines") + prompt: str | None = Field(default=None, description="User prompt for prompt-driven pipelines") status: PipelineStatus = Field( default=PipelineStatus.PENDING, description="Overall pipeline status" ) diff --git a/orchestrator/routes/phases.py b/orchestrator/routes/phases.py index 153573c177..40ca86f7e9 100644 --- a/orchestrator/routes/phases.py +++ b/orchestrator/routes/phases.py @@ -46,7 +46,7 @@ def get_logger(name: str, **kwargs) -> logging.Logger: # type: ignore[misc] phases_bp = Blueprint("phases", __name__, url_prefix="/api/v1/pipelines") -# Valid phase transitions for issue-driven pipelines +# Valid phase transitions PHASE_TRANSITIONS = { PipelinePhase.REFINE: [PipelinePhase.PLAN, PipelinePhase.IMPLEMENT], PipelinePhase.PLAN: [PipelinePhase.IMPLEMENT], @@ -54,21 +54,6 @@ def get_logger(name: str, **kwargs) -> logging.Logger: # type: ignore[misc] PipelinePhase.PR: [], # Terminal phase } -# Valid phase transitions for local pipelines -LOCAL_PHASE_TRANSITIONS = { - PipelinePhase.REFINE: [PipelinePhase.PLAN, PipelinePhase.IMPLEMENT], - PipelinePhase.PLAN: [PipelinePhase.IMPLEMENT], - PipelinePhase.IMPLEMENT: [PipelinePhase.PR], - PipelinePhase.PR: [], # Terminal phase -} - - -def get_phase_transitions(pipeline_mode: str = "issue") -> dict: - """Get the phase transition map for a pipeline mode.""" - if pipeline_mode == "local": - return LOCAL_PHASE_TRANSITIONS - return PHASE_TRANSITIONS - def make_error_response( message: str, @@ -122,19 +107,17 @@ def _clear_concurrent_state(pipeline_id: str) -> None: def validate_phase_transition( current_phase: PipelinePhase, target_phase: PipelinePhase, - pipeline_mode: str = "issue", ) -> tuple[bool, str]: """Validate a phase transition. Args: current_phase: Current pipeline phase target_phase: Target phase to transition to - pipeline_mode: Pipeline mode ("issue" or "local") Returns: Tuple of (is_valid, error_message) """ - transitions = get_phase_transitions(pipeline_mode) + transitions = PHASE_TRANSITIONS if target_phase not in transitions.get(current_phase, []): valid_targets = transitions.get(current_phase, []) if not valid_targets: @@ -255,10 +238,7 @@ def advance_phase(pipeline_id: str) -> tuple[Response, int]: # Validate transition unless forced if not force: - pipeline_mode = getattr(pipeline, "mode", "issue") - is_valid, error = validate_phase_transition( - previous_phase, target_phase, pipeline_mode=pipeline_mode - ) + is_valid, error = validate_phase_transition(previous_phase, target_phase) if not is_valid: return make_error_response(error, status_code=400) @@ -481,10 +461,8 @@ def complete_phase(pipeline_id: str) -> tuple[Response, int]: if data.get("artifacts"): phase_execution.artifacts = data["artifacts"] - # Determine next phase (respects pipeline mode) - pipeline_mode = getattr(pipeline, "mode", "issue") - transitions = get_phase_transitions(pipeline_mode) - next_phases = transitions.get(pipeline.current_phase, []) + # Determine next phase + next_phases = PHASE_TRANSITIONS.get(pipeline.current_phase, []) next_phase = next_phases[0] if next_phases else None store.save_pipeline(pipeline, expected_version=original_version) diff --git a/orchestrator/routes/pipelines.py b/orchestrator/routes/pipelines.py index 8c9e8b7f2e..706640aa43 100644 --- a/orchestrator/routes/pipelines.py +++ b/orchestrator/routes/pipelines.py @@ -494,73 +494,32 @@ def create_pipeline() -> tuple[Response, int]: if not data: return make_error_response("Missing request body") - mode = data.get("mode", "issue") - network_mode = data.get("network_mode") if network_mode is not None and network_mode not in ("public", "private"): return make_error_response( f"Invalid network_mode: {network_mode!r} (must be 'public' or 'private')" ) - if mode == "local": - # Local mode: prompt and repo required - prompt = data.get("prompt") - if not prompt: - return make_error_response("Missing prompt (required for local mode)") - if not data.get("repo"): - return make_error_response("Missing repo (required for local mode)") - - # Local pipelines always use the base EGG_REPO_PATH — not a repo-specific - # subdirectory — so that list/get/start resolve to the same path. - repo_path = Path(os.environ.get("EGG_REPO_PATH", ".")) - if not repo_path.is_absolute(): - repo_path = Path.cwd() / repo_path - - try: - store = get_state_store(repo_path) - pipeline = store.create_pipeline( - issue_number=data.get("issue_number"), - repo=data.get("repo"), - branch=data.get("branch"), - config=data.get("config"), - mode="local", - prompt=prompt, - network_mode=network_mode, - ) - - # Contract creation is deferred to _run_pipeline so it writes - # into the per-pipeline worktree instead of the main repo. - - logger.info( - "Local pipeline created", - pipeline_id=pipeline.id, - prompt=prompt[:100], - ) - - return make_success_response( - "Pipeline created", - data={"pipeline": pipeline.model_dump(mode="json")}, - ) - - except StateStoreError as e: - if "already exists" in str(e): - return make_error_response(str(e), status_code=409) - logger.error("Failed to create local pipeline", error=str(e)) - return make_error_response(f"Failed to create pipeline: {e}", status_code=500) - - # Issue mode: existing behavior issue_number = data.get("issue_number") repo = data.get("repo") branch = data.get("branch") + prompt = data.get("prompt") - if not issue_number: - return make_error_response("Missing issue_number") if not repo: return make_error_response("Missing repo") - if not branch: + + # Issue-driven pipelines require a branch; prompt-driven ones do not + if issue_number and not branch: return make_error_response("Missing branch") - repo_path = get_repo_path() + # Prompt-driven pipelines use the base EGG_REPO_PATH so that + # list/get/start resolve to the same path. + if not issue_number: + repo_path = Path(os.environ.get("EGG_REPO_PATH", ".")) + if not repo_path.is_absolute(): + repo_path = Path.cwd() / repo_path + else: + repo_path = get_repo_path() try: store = get_state_store(repo_path) @@ -569,7 +528,7 @@ def create_pipeline() -> tuple[Response, int]: repo=repo, branch=branch, config=data.get("config"), - mode="issue", + prompt=prompt, network_mode=network_mode, ) @@ -1263,61 +1222,44 @@ def _get_reviewer_scope_preamble(reviewer_type: str, phase: str) -> str: def _verdict_path_for_type( phase: str, reviewer_type: str, - pipeline_mode: str, issue_number: int | None = None, pipeline_id: str | None = None, plan_phase_id: str | None = None, ) -> str: """Return the relative verdict file path for a given reviewer type. - For issue mode, uses issue number as prefix (e.g., 123-implement-code-review.json). - For local mode, uses pipeline_id as prefix (e.g., local-abc12345-refine-refine-review.json). + Uses issue_number as prefix when available, otherwise pipeline_id. When plan_phase_id is provided (Tier 3 phase-level dispatch), it is included in the path to avoid race conditions between parallel phase reviewers: e.g., 123-implement-phase-1-code-review.json. """ phase_segment = f"{phase}-{plan_phase_id}" if plan_phase_id else phase - if pipeline_mode == "local": - prefix = pipeline_id if pipeline_id else "local" - return f".egg-state/reviews/{prefix}-{phase_segment}-{reviewer_type}-review.json" - else: - return f".egg-state/reviews/{issue_number}-{phase_segment}-{reviewer_type}-review.json" + prefix = _pipeline_identifier(issue_number, pipeline_id or "unknown") + return f".egg-state/reviews/{prefix}-{phase_segment}-{reviewer_type}-review.json" def _get_draft_path( phase: str, - pipeline_mode: str, issue_number: int | None = None, pipeline_id: str | None = None, ) -> str | None: """Return relative path to the draft file for a phase. - For issue mode, uses issue number as prefix (e.g., 123-analysis.md). - For local mode, uses pipeline_id as prefix (e.g., local-abc12345-analysis.md). + Uses issue_number as prefix when available, otherwise pipeline_id. """ - is_local = pipeline_mode == "local" - if is_local: - prefix = pipeline_id if pipeline_id else "local" - if phase == "refine": - return f".egg-state/drafts/{prefix}-analysis.md" - elif phase == "implement": - return None - else: - return f".egg-state/drafts/{prefix}-{phase}.md" + prefix = _pipeline_identifier(issue_number, pipeline_id or "unknown") + if phase == "refine": + return f".egg-state/drafts/{prefix}-analysis.md" + elif phase == "implement": + return None else: - if phase == "refine": - return f".egg-state/drafts/{issue_number}-analysis.md" - elif phase == "implement": - return None - else: - return f".egg-state/drafts/{issue_number}-{phase}.md" + return f".egg-state/drafts/{prefix}-{phase}.md" def _read_phase_draft( repo_path: Path, phase: str, - pipeline_mode: str, issue_number: int | None = None, pipeline_id: str | None = None, max_chars: int = 32000, @@ -1327,7 +1269,7 @@ def _read_phase_draft( Returns None when the draft cannot be found (no path configured or file missing on disk). """ - draft_rel = _get_draft_path(phase, pipeline_mode, issue_number, pipeline_id) + draft_rel = _get_draft_path(phase, issue_number=issue_number, pipeline_id=pipeline_id) if not draft_rel: return None draft_path = repo_path / draft_rel @@ -1542,10 +1484,8 @@ def _render_contract_tasks( except ImportError: return None - # Mirror _populate_contract_from_plan logic for contract identifier - contract_id: int | str = pipeline_id - if pipeline_mode != "local" and issue_number: - contract_id = issue_number + # Use issue_number as contract identifier when available + contract_id: int | str = _pipeline_identifier(issue_number, pipeline_id) try: contract = load_contract(contract_id, Path(repo_path)) @@ -1583,7 +1523,7 @@ def _check_short_circuit_signal( Looks for the *last* fenced YAML block containing ``short_circuit: true`` in the analysis. Returns True if found. """ - draft_rel = _get_draft_path("refine", pipeline_mode, issue_number, pipeline_id) + draft_rel = _get_draft_path("refine", issue_number=issue_number, pipeline_id=pipeline_id) if not draft_rel: return False draft_path = repo_path / draft_rel @@ -1623,7 +1563,7 @@ def _check_high_complexity_signal( complexity_tier is one of "low", "mid", "high". Defaults to ("mid", False) if no signal is found. """ - draft_rel = _get_draft_path("refine", pipeline_mode, issue_number, pipeline_id) + draft_rel = _get_draft_path("refine", issue_number=issue_number, pipeline_id=pipeline_id) if not draft_rel: return "mid", False draft_path = repo_path / draft_rel @@ -1682,14 +1622,13 @@ def _build_review_prompt( Tells the reviewer to evaluate the draft for the given phase and write a typed verdict JSON file to .egg-state/reviews/. """ - draft_path = _get_draft_path(phase, pipeline_mode, issue_number, pipeline_id) + draft_path = _get_draft_path(phase, issue_number=issue_number, pipeline_id=pipeline_id) verdict_path = _verdict_path_for_type( phase, reviewer_type, - pipeline_mode, - issue_number, - pipeline_id, + issue_number=issue_number, + pipeline_id=pipeline_id, plan_phase_id=plan_phase_id, ) @@ -1914,9 +1853,8 @@ def _read_review_verdict( verdict_rel = _verdict_path_for_type( phase, reviewer_type, - pipeline_mode, - issue_number, - pipeline_id, + issue_number=issue_number, + pipeline_id=pipeline_id, plan_phase_id=plan_phase_id, ) verdict_file = repo_path / verdict_rel @@ -2479,17 +2417,13 @@ def _build_phase_prompt( """Build a phase-specific prompt for the sandbox Claude invocation. Follows a structured prompt format: - Context → Task → Restrictions → Completion. Adapted for the - orchestrator (local mode has no GitHub issue, contract, or PR). + Context → Task → Restrictions → Completion. """ - is_local = pipeline_mode == "local" - # --- Context header --- lines = [f"You are in the **{phase}** phase of the SDLC pipeline.\n"] lines.append("## Context\n") lines.append(f"Pipeline ID: {pipeline_id}") lines.append(f"Phase: {phase}") - lines.append(f"Mode: {pipeline_mode}") if repo: lines.append(f"Repository: {repo}") if branch: @@ -2529,8 +2463,8 @@ def _build_phase_prompt( lines.append("## Your Task\n") # Get the correct draft path based on mode - analysis_path = _get_draft_path("refine", pipeline_mode, issue_number, pipeline_id) - plan_path = _get_draft_path("plan", pipeline_mode, issue_number, pipeline_id) + analysis_path = _get_draft_path("refine", issue_number=issue_number, pipeline_id=pipeline_id) + plan_path = _get_draft_path("plan", issue_number=issue_number, pipeline_id=pipeline_id) if phase == "refine": lines.extend( @@ -2730,7 +2664,6 @@ def _build_phase_prompt( draft_text = _read_phase_draft( Path(repo_path), draft_phase, - pipeline_mode, issue_number=issue_number, pipeline_id=pipeline_id, ) @@ -2844,7 +2777,7 @@ def _build_phase_prompt( # --- Phase restrictions --- lines.append("## Phase Restrictions\n") - if is_local and phase in ("refine", "plan"): + if issue_number is None and phase in ("refine", "plan"): lines.extend( [ "In this phase:", @@ -2860,7 +2793,7 @@ def _build_phase_prompt( "", ] ) - elif is_local and phase == "implement": + elif issue_number is None and phase == "implement": lines.extend( [ "In this phase:", @@ -3115,7 +3048,7 @@ def _build_agent_prompt( ] ) elif role_value == "task_planner": - draft_path = _get_draft_path("plan", pipeline_mode, issue_number, pipeline_id) + draft_path = _get_draft_path("plan", issue_number=issue_number, pipeline_id=pipeline_id) lines.extend( [ "Decompose the architecture analysis into a single-PR implementation plan.", @@ -3344,11 +3277,12 @@ def _build_phase_scoped_prompt( draft_text = _read_phase_draft( worktree_repo_path, "plan", - pipeline_mode, issue_number=pipeline.issue_number, pipeline_id=pipeline_id, ) - draft_rel = _get_draft_path("plan", pipeline_mode, pipeline.issue_number, pipeline_id) + draft_rel = _get_draft_path( + "plan", issue_number=pipeline.issue_number, pipeline_id=pipeline_id + ) if draft_text: overview = _extract_plan_overview(draft_text) if overview: @@ -3474,7 +3408,7 @@ def _run_tier3_implement( save_contract, ) - pipeline_mode = pipeline.mode or "issue" + pipeline_mode = "issue" if pipeline.issue_number is not None else "prompt" contract_key = _pipeline_identifier(pipeline.issue_number, pipeline_id) # Ensure contract exists in the worktree. Agent git checkout in a @@ -4260,7 +4194,7 @@ def _run_multi_agent_phase( from egg_contracts.orchestration import initialize_orchestration # Get pipeline mode - pipeline_mode = pipeline.mode or "issue" + pipeline_mode = "issue" if pipeline.issue_number is not None else "prompt" # Build agent-specific prompts for all roles in this phase. # Note: phase_obj / all_phases are not passed here because Tier 2 @@ -4346,7 +4280,7 @@ def spawn_fn(role: AgentRole, prompt_text: str, extra_env: dict[str, str]) -> tu # because orchestration state is re-initialized below via # initialize_orchestration(), and phase handoff data is persisted # separately via save_agent_output(). - from egg_contracts.loader import contract_exists, create_contract, create_local_contract + from egg_contracts.loader import contract_exists, create_contract contract_key = _pipeline_identifier(pipeline.issue_number, pipeline_id) if not contract_exists(contract_key, worktree_repo_path): @@ -4356,28 +4290,20 @@ def spawn_fn(role: AgentRole, prompt_text: str, extra_env: dict[str, str]) -> tu phase=phase, contract_key=contract_key, ) - if pipeline_mode == "local": - create_local_contract( + if pipeline.issue_number is not None: + issue_url = f"https://github.com/{pipeline.repo}/issues/{pipeline.issue_number}" + create_contract( + issue_number=pipeline.issue_number, + title=f"Issue #{pipeline.issue_number}", + url=issue_url, + repo_root=worktree_repo_path, + ) + else: + create_contract( pipeline_id=str(contract_key), title=(pipeline.prompt or "")[:100], repo_root=worktree_repo_path, ) - else: - if pipeline.issue_number is None: - logger.error( - "Cannot recreate contract: issue-mode pipeline has no issue_number", - pipeline_id=pipeline_id, - ) - # Fall through — dispatcher will raise ContractNotFoundError - # which is handled by the existing catch in the completion handler - else: - issue_url = f"https://github.com/{pipeline.repo}/issues/{pipeline.issue_number}" - create_contract( - issue_number=pipeline.issue_number, - title=f"Issue #{pipeline.issue_number}", - url=issue_url, - repo_root=worktree_repo_path, - ) # Create dispatcher and executor. # The contract's orchestration state defaults to implement-phase roles @@ -4480,7 +4406,7 @@ def _run_concurrent_phase( from ..concurrent_executor import ConcurrentPhaseExecutor # type: ignore phase_str = phase if isinstance(phase, str) else phase.value - pipeline_mode = pipeline.mode or "issue" + pipeline_mode = "issue" if pipeline.issue_number is not None else "prompt" # Build per-role prompts (matches _run_multi_agent_phase pattern). roles = [AgentRole.CODER, AgentRole.TESTER, AgentRole.DOCUMENTER] @@ -5424,12 +5350,11 @@ def _synthesize_plan_draft( has not already written one) so that _populate_contract_from_plan() and the HITL gate can find it. """ - draft_rel = _get_draft_path("plan", pipeline_mode, issue_number, pipeline_id) + draft_rel = _get_draft_path("plan", issue_number=issue_number, pipeline_id=pipeline_id) if not draft_rel: logger.debug( "No draft path for plan phase, skipping synthesis", pipeline_id=pipeline_id, - pipeline_mode=pipeline_mode, ) return @@ -5541,17 +5466,8 @@ def _populate_contract_from_plan( logger.warning("egg_contracts not available, skipping contract population") return - # Guard against issue-mode pipelines missing issue_number — _get_draft_path - # would produce a path containing literal "None" (e.g. .egg-state/drafts/None-plan.md). - if pipeline_mode != "local" and not issue_number: - logger.warning( - "Issue-mode pipeline missing issue_number, skipping contract population", - pipeline_id=pipeline_id, - ) - return - - # Resolve draft path based on pipeline mode - draft_rel = _get_draft_path("plan", pipeline_mode, issue_number, pipeline_id) + # Resolve draft path + draft_rel = _get_draft_path("plan", issue_number=issue_number, pipeline_id=pipeline_id) if not draft_rel: logger.warning("No draft path for plan phase", pipeline_id=pipeline_id) return @@ -5561,10 +5477,8 @@ def _populate_contract_from_plan( logger.warning("Plan draft not found, skipping contract population", path=str(plan_path)) return - # For issue mode, use issue number as the contract identifier - contract_id: int | str = pipeline_id - if pipeline_mode != "local" and issue_number: - contract_id = issue_number + # Use issue_number as contract identifier when available + contract_id: int | str = _pipeline_identifier(issue_number, pipeline_id) try: contract = load_contract(contract_id, repo_path) @@ -5640,13 +5554,6 @@ def _sync_pipeline_decisions_to_contract( logger.warning("egg_contracts not available, skipping decision sync") return - if pipeline_mode != "local" and not issue_number: - logger.warning( - "Issue-mode pipeline missing issue_number, skipping decision sync", - pipeline_id=pipeline_id, - ) - return - # Load pipeline to get its decisions store = get_state_store(repo_path) try: @@ -5669,10 +5576,8 @@ def _sync_pipeline_decisions_to_contract( logger.debug("No substantive decisions to sync", pipeline_id=pipeline_id) return - # Load contract - contract_id: int | str = pipeline_id - if pipeline_mode != "local" and issue_number: - contract_id = issue_number + # Use issue_number as contract identifier when available + contract_id: int | str = _pipeline_identifier(issue_number, pipeline_id) try: contract = load_contract(contract_id, repo_path) @@ -5750,7 +5655,7 @@ def _run_pipeline(pipeline_id: str, repo_path: Path) -> None: pipeline_id: Pipeline ID repo_path: Path to repository """ - from routes.phases import get_phase_transitions + from routes.phases import PHASE_TRANSITIONS # Track which run of the pipeline this thread owns. If the pipeline # is deleted and recreated with the same ID while we're still running, @@ -5763,8 +5668,8 @@ def _run_pipeline(pipeline_id: str, repo_path: Path) -> None: spawner = get_container_spawner() pipeline = store.load_pipeline(pipeline_id) run_created_at = pipeline.created_at - pipeline_mode = getattr(pipeline, "mode", "issue") - transitions = get_phase_transitions(pipeline_mode) + pipeline_mode = "issue" if pipeline.issue_number is not None else "prompt" + transitions = PHASE_TRANSITIONS # Apply environment variable overrides for multi-agent config. # These come from CLI flags (--multi-agent, --max-parallel) passed @@ -5782,13 +5687,10 @@ def _run_pipeline(pipeline_id: str, repo_path: Path) -> None: env_max_parallel, ) - # Map pipeline mode to gateway session mode. + # Map pipeline to gateway session mode. # If the pipeline has an explicit network_mode (e.g. "private"), use it; - # otherwise fall back to the default mapping. - if pipeline.network_mode: - gateway_mode = pipeline.network_mode - else: - gateway_mode = "local" if pipeline_mode == "local" else "public" + # otherwise default to "public". + gateway_mode = pipeline.network_mode or "public" # Parse host repo map for volume mounts. When the orchestrator # runs inside Docker, EGG_REPO_PATH is the *container* path but @@ -5946,17 +5848,9 @@ def _run_pipeline(pipeline_id: str, repo_path: Path) -> None: # creation so it doesn't pollute the main repo working directory). if not pipeline.contract_synced: try: - if pipeline_mode == "local": - from egg_contracts.loader import create_local_contract - - create_local_contract( - pipeline_id=pipeline.id, - title=(pipeline.prompt or "")[:100], - repo_root=worktree_repo_path, - ) - else: - from egg_contracts.loader import create_contract + from egg_contracts.loader import create_contract + if pipeline.issue_number is not None: issue_url = f"https://github.com/{pipeline.repo}/issues/{pipeline.issue_number}" create_contract( issue_number=pipeline.issue_number, @@ -5964,6 +5858,12 @@ def _run_pipeline(pipeline_id: str, repo_path: Path) -> None: url=issue_url, repo_root=worktree_repo_path, ) + else: + create_contract( + pipeline_id=pipeline.id, + title=(pipeline.prompt or "")[:100], + repo_root=worktree_repo_path, + ) pipeline.contract_synced = True # Commit all .egg-state/ files so they're on the feature branch @@ -6114,7 +6014,7 @@ def _run_pipeline(pipeline_id: str, repo_path: Path) -> None: # GATEWAY_URL, RUNTIME_UID/GID, proxy vars, DNS lockdown, and # extra_hosts are now handled by the shared build_sandbox_config() # inside spawn_agent_container(). Only pipeline-specific vars go here. - if gateway_mode in ("private", "local"): + if gateway_mode == "private": orchestrator_ip = ORCHESTRATOR_ISOLATED_IP else: orchestrator_ip = ORCHESTRATOR_EXTERNAL_IP @@ -6732,9 +6632,8 @@ def _run_pipeline(pipeline_id: str, repo_path: Path) -> None: verdict_rel = _verdict_path_for_type( current_phase.value, rtype, - pipeline_mode, - pipeline.issue_number, - pipeline_id, + issue_number=pipeline.issue_number, + pipeline_id=pipeline_id, ) verdict_path = worktree_repo_path / verdict_rel if verdict_path.exists(): @@ -7071,9 +6970,8 @@ def _spawn_reviewer( # type: ignore[no-untyped-def] draft_content = _read_phase_draft( worktree_repo_path, current_phase.value, - pipeline_mode, - pipeline.issue_number, - pipeline_id, + issue_number=pipeline.issue_number, + pipeline_id=pipeline_id, ) phase_label = "analysis" if current_phase.value == "refine" else current_phase.value @@ -7319,8 +7217,7 @@ def _spawn_reviewer( # type: ignore[no-untyped-def] with get_pipeline_state_lock(pipeline_id): pipeline = store.load_pipeline(pipeline_id) pipeline.status = PipelineStatus.COMPLETE - is_local = pipeline_mode == "local" - store.save_pipeline(pipeline, force_commit=is_local) + store.save_pipeline(pipeline, force_commit=(pipeline.issue_number is None)) # Report pipeline completion to collaborator report_pipeline_status( @@ -7346,8 +7243,7 @@ def _spawn_reviewer( # type: ignore[no-untyped-def] plan_execution.status = PipelineStatus.COMPLETE plan_execution.completed_at = datetime.utcnow() plan_execution.error = "skipped: short-circuit" - is_local = pipeline_mode == "local" - store.save_pipeline(pipeline, force_commit=is_local) + store.save_pipeline(pipeline, force_commit=(pipeline.issue_number is None)) logger.info( "Phase advanced", @@ -7502,8 +7398,6 @@ def start_pipeline(pipeline_id: str) -> tuple[Response, int]: # No pending decisions — the polling thread died (e.g. restart) # but the human already resolved everything. Recover based on # the latest phase_gate decision's resolution. - from routes.phases import get_phase_transitions - with get_pipeline_state_lock(pipeline_id): pipeline = store.load_pipeline(pipeline_id) @@ -7547,8 +7441,9 @@ def start_pipeline(pipeline_id: str) -> tuple[Response, int]: if phase_execution.completed_at is None: phase_execution.completed_at = datetime.utcnow() - pipeline_mode = getattr(pipeline, "mode", "issue") - transitions = get_phase_transitions(pipeline_mode) + from routes.phases import PHASE_TRANSITIONS + + transitions = PHASE_TRANSITIONS current_phase = pipeline.current_phase next_phases = transitions.get(current_phase, []) diff --git a/orchestrator/state_store.py b/orchestrator/state_store.py index bf1a464e3e..1425b1d91f 100644 --- a/orchestrator/state_store.py +++ b/orchestrator/state_store.py @@ -484,10 +484,10 @@ def save_pipeline( with path.open("w") as f: f.write(pipeline.model_dump_json(indent=2)) - # For local pipelines, only commit if force_commit is True (phase boundaries) - # For issue pipelines, always commit when commit=True - is_local = getattr(pipeline, "mode", "issue") == "local" - should_commit = commit and (not is_local or force_commit) + # For prompt-driven pipelines (no issue_number), only commit if force_commit + # is True (phase boundaries). For issue pipelines, always commit when commit=True. + is_prompt_driven = pipeline.issue_number is None + should_commit = commit and (not is_prompt_driven or force_commit) if should_commit: self._commit_state(pipeline, message) @@ -662,7 +662,6 @@ def create_pipeline( repo: str | None = None, branch: str | None = None, config: dict[str, Any] | None = None, - mode: str = "issue", prompt: str | None = None, pipeline_id: str | None = None, network_mode: str | None = None, @@ -670,12 +669,11 @@ def create_pipeline( """Create a new pipeline. Args: - issue_number: GitHub issue number (required for issue mode) - repo: Repository in owner/name format (required for issue mode) - branch: Work branch name (required for issue mode) + issue_number: GitHub issue number (optional) + repo: Repository in owner/name format + branch: Work branch name config: Optional pipeline configuration - mode: Pipeline mode - "issue" or "local" - prompt: User prompt (required for local mode) + prompt: User prompt (for prompt-driven pipelines) pipeline_id: Explicit pipeline ID (auto-generated if not provided) network_mode: Network mode for spawned containers ("public", "private", or None) @@ -685,13 +683,11 @@ def create_pipeline( Raises: StateStoreError: If pipeline already exists """ - if mode == "local": - if not pipeline_id: - pipeline_id = f"local-{os.urandom(4).hex()}" - else: - if not issue_number: - raise StateStoreError("issue_number is required for issue-mode pipelines") - pipeline_id = pipeline_id or f"issue-{issue_number}" + if not pipeline_id: + if issue_number: + pipeline_id = f"issue-{issue_number}" + else: + pipeline_id = f"pipeline-{os.urandom(4).hex()}" if self.pipeline_exists(pipeline_id): raise StateStoreError(f"Pipeline {pipeline_id} already exists") @@ -701,7 +697,6 @@ def create_pipeline( issue_number=issue_number, repo=repo, branch=branch, - mode=mode, prompt=prompt, network_mode=network_mode, # Contract is created separately — mark as unsynced until verified @@ -713,11 +708,7 @@ def create_pipeline( pipeline.config = PipelineConfig.model_validate(config) - commit_msg = ( - f"Create local pipeline {pipeline_id}" - if mode == "local" - else f"Create pipeline for issue #{issue_number}" - ) + commit_msg = f"Create pipeline {pipeline_id}" self.save_pipeline(pipeline, message=commit_msg) return pipeline diff --git a/orchestrator/tests/test_container_monitor.py b/orchestrator/tests/test_container_monitor.py index c9e524706a..240fa7e47b 100644 --- a/orchestrator/tests/test_container_monitor.py +++ b/orchestrator/tests/test_container_monitor.py @@ -273,7 +273,6 @@ def _make_pipeline_with_coordinator_agent(container_id: str = "coord_agent_123") pipeline = Pipeline( id="local-99", repo="owner/repo", - mode="local", status=PipelineStatus.RUNNING, current_phase=PipelinePhase.IMPLEMENT, coordinator_state=CoordinatorState( diff --git a/orchestrator/tests/test_coordinator_gaps.py b/orchestrator/tests/test_coordinator_gaps.py index 744c5e6a1c..f14002ed11 100644 --- a/orchestrator/tests/test_coordinator_gaps.py +++ b/orchestrator/tests/test_coordinator_gaps.py @@ -773,7 +773,7 @@ def test_list_tasks_excludes_non_coordinator_pipelines(self): assert result["total"] == 0 def test_submit_task_with_issue_number(self): - """submit_task with issue sets mode='issue' and auto-generates branch.""" + """submit_task with issue sets issue_number.""" handler = CoordinatorToolHandler() with patch.object(handler, "_make_request") as mock_req: mock_req.return_value = {"data": {"pipeline": {"id": "issue-42"}}} @@ -781,7 +781,7 @@ def test_submit_task_with_issue_number(self): # First call is the pipeline create; second is /start assert mock_req.call_count == 2 call_data = mock_req.call_args_list[0][1]["data"] - assert call_data["mode"] == "issue" + assert "mode" not in call_data assert call_data["issue_number"] == 42 assert call_data["branch"] == "egg/issue-42" assert result["task_id"] == "issue-42" @@ -790,7 +790,7 @@ def test_submit_task_with_issue_number(self): assert start_call[1]["method"] == "POST" def test_submit_task_with_issue_and_branch_override(self): - """submit_task with issue_number and explicit branch uses the override.""" + """submit_task with issue_number and explicit branch uses provided branch.""" handler = CoordinatorToolHandler() with patch.object(handler, "_make_request") as mock_req: mock_req.return_value = {"data": {"pipeline": {"id": "issue-42"}}} @@ -798,12 +798,12 @@ def test_submit_task_with_issue_and_branch_override(self): {"description": "Fix bug", "issue_number": 42, "branch": "egg/my-branch"} ) call_data = mock_req.call_args_list[0][1]["data"] - assert call_data["mode"] == "issue" + assert "mode" not in call_data assert call_data["issue_number"] == 42 assert call_data["branch"] == "egg/my-branch" def test_submit_task_without_issue_number(self): - """submit_task without issue sets mode='local' and includes prompt.""" + """submit_task without issue includes prompt.""" handler = CoordinatorToolHandler() with patch.object(handler, "_make_request") as mock_req: mock_req.return_value = {"data": {"pipeline": {"id": "local-abc123"}}} @@ -811,7 +811,7 @@ def test_submit_task_without_issue_number(self): # First call is the pipeline create; second is /start assert mock_req.call_count == 2 call_data = mock_req.call_args_list[0][1]["data"] - assert call_data["mode"] == "local" + assert "mode" not in call_data assert call_data["prompt"] == "Refactor auth" start_call = mock_req.call_args_list[1] assert "/start" in start_call[0][0] diff --git a/orchestrator/tests/test_coordinator_mcp_functional.py b/orchestrator/tests/test_coordinator_mcp_functional.py index 30b0892da7..a7d6b8d81f 100644 --- a/orchestrator/tests/test_coordinator_mcp_functional.py +++ b/orchestrator/tests/test_coordinator_mcp_functional.py @@ -431,7 +431,7 @@ def test_submit_task_with_issue(self, mock_req): assert mock_req.call_count == 2 call_data = mock_req.call_args_list[0][1]["data"] assert call_data["issue_number"] == 42 - assert call_data["mode"] == "issue" + assert "mode" not in call_data assert call_data["branch"] == "egg/issue-42" start_call = mock_req.call_args_list[1] assert "/start" in start_call[0][0] @@ -452,6 +452,7 @@ def test_submit_task_with_issue_and_branch_override(self, mock_req): ) assert result["task_id"] == "issue-42" call_data = mock_req.call_args_list[0][1]["data"] + assert call_data["issue_number"] == 42 assert call_data["branch"] == "egg/custom-branch" @patch.object(CoordinatorToolHandler, "_make_request") @@ -466,7 +467,7 @@ def test_submit_task_without_issue(self, mock_req): # First call is the pipeline create; second is /start assert mock_req.call_count == 2 call_data = mock_req.call_args_list[0][1]["data"] - assert call_data["mode"] == "local" + assert "mode" not in call_data assert call_data["prompt"] == "Improve performance" start_call = mock_req.call_args_list[1] assert "/start" in start_call[0][0] @@ -490,7 +491,6 @@ def test_get_status(self, mock_req): "repo": "owner/repo", "issue_number": 42, "created_at": "2026-03-13T00:00:00Z", - "mode": "local", } } }, diff --git a/orchestrator/tests/test_hitl_revision.py b/orchestrator/tests/test_hitl_revision.py index c8558faa12..6934fc45f1 100644 --- a/orchestrator/tests/test_hitl_revision.py +++ b/orchestrator/tests/test_hitl_revision.py @@ -1076,7 +1076,7 @@ def capture_save(c, repo_path): _sync_pipeline_decisions_to_contract( repo_path=Path("/fake/repo"), pipeline_id="test-pipeline", - pipeline_mode="local", + pipeline_mode="issue", ) # --- Verify --- diff --git a/orchestrator/tests/test_pipeline_prompts.py b/orchestrator/tests/test_pipeline_prompts.py index e80d03b37e..d81f36b1cc 100644 --- a/orchestrator/tests/test_pipeline_prompts.py +++ b/orchestrator/tests/test_pipeline_prompts.py @@ -418,7 +418,7 @@ def test_plan_embedded_when_repo_path_and_draft_exist(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget", repo_path=tmpdir, ) @@ -433,7 +433,7 @@ def test_fallback_when_repo_path_is_none(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget", repo_path=None, ) @@ -465,7 +465,7 @@ def test_fallback_when_draft_file_missing(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget", repo_path=tmpdir, ) @@ -486,7 +486,7 @@ def test_revision_cycle_omits_plan(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget", repo_path=tmpdir, review_cycle=1, @@ -501,7 +501,7 @@ def test_revision_cycle_omits_task_description(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget with many features", review_cycle=1, review_feedback="Fix naming", @@ -514,7 +514,7 @@ def test_revision_cycle_contains_revision_instructions(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget", review_cycle=1, review_feedback="Fix the naming convention", @@ -528,7 +528,7 @@ def test_revision_cycle_includes_review_feedback(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget", review_cycle=1, review_feedback="Variable naming is inconsistent", @@ -541,7 +541,7 @@ def test_cycle_0_includes_task_description(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget with many features", review_cycle=0, ) @@ -553,7 +553,7 @@ def test_revision_cycle_without_feedback_includes_task_description(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget with many features", review_cycle=1, review_feedback=None, @@ -1619,7 +1619,7 @@ def test_prompt_falls_back_to_pipeline_id(self): role_value="architect", phase="plan", pipeline_id="local-abc123", - pipeline_mode="local", + pipeline_mode="issue", prompt="# Feature", issue_number=None, ) @@ -2062,7 +2062,7 @@ def test_phase_prompt_revision_reviewer_only_no_tester_language(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget", review_cycle=1, review_feedback="Fix the naming convention", @@ -2083,7 +2083,7 @@ def test_phase_prompt_revision_with_tester_findings(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget", review_cycle=1, review_feedback=feedback, @@ -2098,7 +2098,7 @@ def test_phase_prompt_revision_no_feedback_does_not_mention_tester(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget", review_cycle=1, review_feedback=None, @@ -2113,7 +2113,7 @@ def test_phase_prompt_prior_feedback_header_with_tester_findings(self): result = _build_phase_prompt( phase="implement", pipeline_id="test-pid", - pipeline_mode="local", + pipeline_mode="issue", prompt="Build a widget", review_cycle=1, review_feedback=feedback, @@ -2194,7 +2194,7 @@ def test_falls_back_to_pipeline_id(self): """Falls back to pipeline_id when issue_number is None.""" result = _build_autofix_prompt( pipeline_id="local-abc", - pipeline_mode="local", + pipeline_mode="issue", check_results={"checks": []}, issue_number=None, ) @@ -2312,14 +2312,14 @@ def test_does_not_overwrite_existing_draft(self, tmp_path): assert draft_path.read_text() == "Existing plan from task_planner" - def test_local_mode_uses_pipeline_id(self, tmp_path): - """In local mode, uses pipeline_id for both draft path and output lookup.""" + def test_uses_pipeline_id_without_issue_number(self, tmp_path): + """Without issue_number, uses pipeline_id for both draft path and output lookup.""" drafts_dir = tmp_path / ".egg-state" / "drafts" drafts_dir.mkdir(parents=True) outputs_dir = tmp_path / ".egg-state" / "agent-outputs" outputs_dir.mkdir(parents=True) - long_content = "Local mode architecture analysis with detailed design decisions, component interactions, and implementation strategy" + long_content = "Architecture analysis with detailed design decisions, component interactions, and implementation strategy" (outputs_dir / "local-abc-architect-output.json").write_text( json.dumps({"content": long_content}) ) @@ -2327,7 +2327,7 @@ def test_local_mode_uses_pipeline_id(self, tmp_path): _synthesize_plan_draft( repo_path=tmp_path, pipeline_id="local-abc", - pipeline_mode="local", + pipeline_mode="issue", issue_number=None, ) @@ -2568,12 +2568,12 @@ def test_prior_feedback_not_shown_on_first_cycle(self): ) assert "## Prior Review Feedback" not in prompt - def test_local_pipeline_mode_verdict_path(self): - """Local pipeline mode uses pipeline_id in verdict path instead of issue number.""" + def test_pipeline_id_in_verdict_path_without_issue(self): + """Uses pipeline_id in verdict path when no issue number is provided.""" prompt = _build_review_prompt( phase="implement", pipeline_id="local-abc12345", - pipeline_mode="local", + pipeline_mode="issue", reviewer_type="code", ) assert "local-abc12345" in prompt diff --git a/orchestrator/tests/test_read_phase_draft.py b/orchestrator/tests/test_read_phase_draft.py index 8488e0a580..895d06f1f0 100644 --- a/orchestrator/tests/test_read_phase_draft.py +++ b/orchestrator/tests/test_read_phase_draft.py @@ -22,7 +22,7 @@ def test_returns_full_content_within_limit(self, tmp_path: Path): drafts.mkdir(parents=True) (drafts / "42-analysis.md").write_text("short content", encoding="utf-8") - result = _read_phase_draft(tmp_path, "refine", "issue", issue_number=42) + result = _read_phase_draft(tmp_path, "refine", issue_number=42) assert result == "short content" def test_truncates_content_exceeding_limit(self, tmp_path: Path): @@ -32,36 +32,36 @@ def test_truncates_content_exceeding_limit(self, tmp_path: Path): content = "x" * 200 (drafts / "42-analysis.md").write_text(content, encoding="utf-8") - result = _read_phase_draft(tmp_path, "refine", "issue", issue_number=42, max_chars=100) + result = _read_phase_draft(tmp_path, "refine", issue_number=42, max_chars=100) assert result.startswith("x" * 100) assert "... (truncated, 200 chars total)" in result def test_no_draft_for_implement_phase(self, tmp_path: Path): """Implement phase has no draft file; returns None.""" - result = _read_phase_draft(tmp_path, "implement", "issue", issue_number=42) + result = _read_phase_draft(tmp_path, "implement", issue_number=42) assert result is None def test_missing_draft_file(self, tmp_path: Path): """Returns None when draft file does not exist on disk.""" - result = _read_phase_draft(tmp_path, "refine", "issue", issue_number=42) + result = _read_phase_draft(tmp_path, "refine", issue_number=42) assert result is None - def test_local_mode_uses_pipeline_id(self, tmp_path: Path): - """Local mode constructs path with pipeline_id prefix.""" + def test_pipeline_id_used_when_no_issue(self, tmp_path: Path): + """Pipeline ID is used as prefix when no issue_number is provided.""" drafts = tmp_path / ".egg-state" / "drafts" drafts.mkdir(parents=True) - (drafts / "pid123-plan.md").write_text("local plan", encoding="utf-8") + (drafts / "pid123-plan.md").write_text("prompt plan", encoding="utf-8") - result = _read_phase_draft(tmp_path, "plan", "local", pipeline_id="pid123") - assert result == "local plan" + result = _read_phase_draft(tmp_path, "plan", pipeline_id="pid123") + assert result == "prompt plan" - def test_local_mode_fallback_prefix(self, tmp_path: Path): - """Local mode without pipeline_id falls back to 'local' prefix.""" + def test_fallback_prefix_without_identifiers(self, tmp_path: Path): + """Without issue_number or pipeline_id, falls back to 'unknown' prefix.""" drafts = tmp_path / ".egg-state" / "drafts" drafts.mkdir(parents=True) - (drafts / "local-analysis.md").write_text("fallback", encoding="utf-8") + (drafts / "unknown-analysis.md").write_text("fallback", encoding="utf-8") - result = _read_phase_draft(tmp_path, "refine", "local") + result = _read_phase_draft(tmp_path, "refine") assert result == "fallback" def test_truncation_suffix_format(self, tmp_path: Path): @@ -71,5 +71,5 @@ def test_truncation_suffix_format(self, tmp_path: Path): content = "a" * 500 (drafts / "7-plan.md").write_text(content, encoding="utf-8") - result = _read_phase_draft(tmp_path, "plan", "issue", issue_number=7, max_chars=10) + result = _read_phase_draft(tmp_path, "plan", issue_number=7, max_chars=10) assert result == "a" * 10 + "\n\n... (truncated, 500 chars total)" diff --git a/orchestrator/tests/test_short_circuit.py b/orchestrator/tests/test_short_circuit.py index ada95cfbeb..f88963568d 100644 --- a/orchestrator/tests/test_short_circuit.py +++ b/orchestrator/tests/test_short_circuit.py @@ -12,7 +12,6 @@ from models import Pipeline, PipelineConfig, PipelinePhase, PipelineStatus from routes.phases import ( - LOCAL_PHASE_TRANSITIONS, PHASE_TRANSITIONS, validate_phase_transition, ) @@ -59,17 +58,6 @@ def test_no_signal_when_draft_missing(self, tmp_path: Path): """Returns False when the draft file doesn't exist.""" assert _check_short_circuit_signal(tmp_path, "issue", issue_number=42) is False - def test_local_mode_signal(self, tmp_path: Path): - """Detects signal in local-mode draft.""" - drafts = tmp_path / ".egg-state" / "drafts" - drafts.mkdir(parents=True) - (drafts / "pid-123-analysis.md").write_text( - "# Analysis\n\n```yaml\n# metadata\nshort_circuit: true\ncomplexity_tier: low\n```\n", - encoding="utf-8", - ) - - assert _check_short_circuit_signal(tmp_path, "local", pipeline_id="pid-123") is True - def test_ignores_yaml_without_short_circuit_key(self, tmp_path: Path): """Returns False when YAML block exists but has no short_circuit key.""" drafts = tmp_path / ".egg-state" / "drafts" @@ -180,27 +168,13 @@ def test_issue_mode_refine_to_implement_valid(self): """REFINE → IMPLEMENT is valid in issue mode transitions.""" assert PipelinePhase.IMPLEMENT in PHASE_TRANSITIONS[PipelinePhase.REFINE] - def test_local_mode_refine_to_implement_valid(self): - """REFINE → IMPLEMENT is valid in local mode transitions.""" - assert PipelinePhase.IMPLEMENT in LOCAL_PHASE_TRANSITIONS[PipelinePhase.REFINE] - def test_refine_to_plan_still_valid(self): """REFINE → PLAN remains valid (default path).""" assert PipelinePhase.PLAN in PHASE_TRANSITIONS[PipelinePhase.REFINE] def test_validate_refine_to_implement(self): """validate_phase_transition accepts REFINE → IMPLEMENT.""" - is_valid, error = validate_phase_transition( - PipelinePhase.REFINE, PipelinePhase.IMPLEMENT, pipeline_mode="issue" - ) - assert is_valid is True - assert error == "" - - def test_validate_refine_to_implement_local(self): - """validate_phase_transition accepts REFINE → IMPLEMENT in local mode.""" - is_valid, error = validate_phase_transition( - PipelinePhase.REFINE, PipelinePhase.IMPLEMENT, pipeline_mode="local" - ) + is_valid, error = validate_phase_transition(PipelinePhase.REFINE, PipelinePhase.IMPLEMENT) assert is_valid is True assert error == "" diff --git a/sandbox/egg_lib/orch_cli.py b/sandbox/egg_lib/orch_cli.py index 38641766f2..4b86a21e70 100755 --- a/sandbox/egg_lib/orch_cli.py +++ b/sandbox/egg_lib/orch_cli.py @@ -401,7 +401,6 @@ def cmd_pipeline_create(args: argparse.Namespace) -> int: """Create a new pipeline.""" data: dict[str, Any] = { "repo": args.repo, - "mode": args.mode, } if args.issue: data["issue_number"] = args.issue @@ -1295,15 +1294,9 @@ def create_parser() -> argparse.ArgumentParser: # pipeline create pl_create = pipeline_sub.add_parser("create", help="Create a pipeline") pl_create.add_argument("--repo", required=True, help="Repository (owner/name)") - pl_create.add_argument( - "--mode", - default="local", - choices=["issue", "local"], - help="Pipeline mode (default: local)", - ) pl_create.add_argument("--issue", type=int, help="Issue number") pl_create.add_argument("--branch", help="Branch name") - pl_create.add_argument("--prompt", help="Prompt (required for local mode)") + pl_create.add_argument("--prompt", help="Prompt (for prompt-driven pipelines)") pl_create.add_argument( "--network-mode", choices=["public", "private"], diff --git a/sandbox/egg_lib/orch_client.py b/sandbox/egg_lib/orch_client.py index df628599d0..7b8e4cd20e 100644 --- a/sandbox/egg_lib/orch_client.py +++ b/sandbox/egg_lib/orch_client.py @@ -141,13 +141,12 @@ def create_pipeline( issue_number: int | None = None, repo: str | None = None, branch: str | None = None, - mode: str = "issue", prompt: str | None = None, config: dict[str, Any] | None = None, network_mode: str | None = None, ) -> dict[str, Any]: """Create a new pipeline.""" - body: dict[str, Any] = {"mode": mode} + body: dict[str, Any] = {} if issue_number is not None: body["issue_number"] = issue_number if repo: diff --git a/sandbox/egg_lib/sdlc_cli.py b/sandbox/egg_lib/sdlc_cli.py index 55e3130029..ea57c29a52 100644 --- a/sandbox/egg_lib/sdlc_cli.py +++ b/sandbox/egg_lib/sdlc_cli.py @@ -393,7 +393,6 @@ def run_local_mode( try: config = {"concurrent_execution": True} if concurrent else None pipeline = client.create_pipeline( - mode="local", prompt=prompt, repo=repo, network_mode=_detect_network_mode(), @@ -420,7 +419,7 @@ def run_local_mode( print(f" {GREEN}Pipeline started.{RESET}\n") # Watch pipeline - result = watch_pipeline(client, pipeline_id, pipeline_mode="local") + result = watch_pipeline(client, pipeline_id, pipeline_mode="prompt") return 0 if result == "complete" else 1 @@ -445,7 +444,6 @@ def _restart_pipeline( issue_number=issue_number, repo=repo, branch=branch, - mode="issue", network_mode=network_mode, config=config, ) @@ -486,7 +484,6 @@ def run_issue_mode( issue_number=issue_number, repo=repo, branch=branch, - mode="issue", network_mode=network_mode, config=config, ) diff --git a/sandbox/egg_lib/sdlc_hitl.py b/sandbox/egg_lib/sdlc_hitl.py index bc4a70545f..6857ce39b6 100644 --- a/sandbox/egg_lib/sdlc_hitl.py +++ b/sandbox/egg_lib/sdlc_hitl.py @@ -33,19 +33,18 @@ def _get_draft_path( phase: str, - pipeline_mode: str, issue_number: int | None = None, pipeline_id: str | None = None, ) -> str | None: """Return relative path to the draft file for a phase. Mirrors the logic in orchestrator/routes/pipelines.py:_get_draft_path. + Uses issue_number as prefix when available, otherwise pipeline_id. """ - is_local = pipeline_mode == "local" - if is_local: - prefix = pipeline_id if pipeline_id else "local" + if issue_number is not None: + prefix = str(issue_number) else: - prefix = str(issue_number) if issue_number else "unknown" + prefix = pipeline_id if pipeline_id else "unknown" if phase == "refine": return f".egg-state/drafts/{prefix}-analysis.md" @@ -127,19 +126,17 @@ def _read_draft(repo_path: Path, draft_rel: str | None) -> str | None: def _get_contract_key( - pipeline_mode: str, issue_number: int | None = None, pipeline_id: str | None = None, ) -> str | None: """Return the contract file key for the current pipeline. - Issue mode uses the issue number as key; local mode uses the pipeline ID. - Returns None if the required identifier is missing. + Uses issue_number when available, otherwise pipeline_id. + Returns None if neither identifier is available. """ - if pipeline_mode == "local": - return pipeline_id if pipeline_id else None - # issue mode (default) - return str(issue_number) if issue_number else None + if issue_number is not None: + return str(issue_number) + return pipeline_id if pipeline_id else None def _load_pending_contract_decisions( @@ -574,7 +571,7 @@ def _handle_phase_gate( while True: # Load pending contract decisions each iteration (may change after answering). # pipeline_id doubles as contract key in local mode (e.g., "local-a1b2c3d4"). - contract_key = _get_contract_key(pipeline_mode, issue_number, pipeline_id) + contract_key = _get_contract_key(issue_number=issue_number, pipeline_id=pipeline_id) pending_contract = ( _load_pending_contract_decisions(repo_path, contract_key) if contract_key else [] ) @@ -1014,7 +1011,7 @@ def resolve_phase_draft( logger.debug("Failed to fetch pipeline phase for decision display", exc_info=True) repo_path = _find_repo_path() - draft_rel = _get_draft_path(phase, pipeline_mode, issue_number, pipeline_id) + draft_rel = _get_draft_path(phase, issue_number=issue_number, pipeline_id=pipeline_id) draft_content = _read_draft(repo_path, draft_rel) # Fall back to decision context if local draft file not found. diff --git a/shared/egg_contracts/loader.py b/shared/egg_contracts/loader.py index 9459355d98..bd6652d52c 100644 --- a/shared/egg_contracts/loader.py +++ b/shared/egg_contracts/loader.py @@ -143,31 +143,31 @@ def contract_exists(identifier: int | str, repo_root: Path | None = None) -> boo def create_contract( - issue_number: int, - title: str, - url: str, + issue_number: int | None = None, + title: str = "", + url: str | None = None, + pipeline_id: str | None = None, repo_root: Path | None = None, initial_phase: PipelinePhase = PipelinePhase.REFINE, ) -> Contract: """ - Create a new contract for an issue. + Create a new contract for a pipeline. Args: - issue_number: The GitHub issue number - title: Issue title - url: Issue URL + issue_number: Optional GitHub issue number + title: Issue/task title + url: Optional issue URL + pipeline_id: Optional pipeline ID (used when no issue_number) repo_root: Optional repository root path initial_phase: Initial pipeline phase Returns: The newly created Contract """ + issue = IssueInfo(number=issue_number, title=title, url=url or "") if issue_number else None contract = Contract( - issue=IssueInfo( - number=issue_number, - title=title, - url=url, - ), + issue=issue, + pipeline_id=pipeline_id if not issue_number else None, current_phase=initial_phase, ) @@ -175,35 +175,6 @@ def create_contract( return contract -def create_local_contract( - pipeline_id: str, - title: str, - repo_root: Path | None = None, - initial_phase: PipelinePhase = PipelinePhase.REFINE, -) -> Contract: - """ - Create a new contract for a local-mode pipeline. - - Args: - pipeline_id: Pipeline ID (e.g., "local-a1b2c3d4") - title: Task description (typically first 100 chars of prompt) - repo_root: Optional repository root path - initial_phase: Initial pipeline phase - - Returns: - The newly created Contract - """ - contract = Contract( - pipeline_id=pipeline_id, - current_phase=initial_phase, - # Use a minimal IssueInfo stand-in for compatibility — no real issue - issue=None, - ) - - save_contract(contract, repo_root) - return contract - - def load_contract_from_branch( identifier: int | str, repo_path: Path, diff --git a/tests/sandbox/test_orch_client.py b/tests/sandbox/test_orch_client.py index 2c1d0cf48f..03b516035d 100644 --- a/tests/sandbox/test_orch_client.py +++ b/tests/sandbox/test_orch_client.py @@ -179,7 +179,7 @@ def test_success(self, stub_server): client, set_resp = stub_server pipeline = {"id": "issue-42", "status": "created"} set_resp({"/api/v1/pipelines": (200, {"data": {"pipeline": pipeline}})}) - result = client.create_pipeline(issue_number=42, repo="org/repo", mode="issue") + result = client.create_pipeline(issue_number=42, repo="org/repo") assert result["id"] == "issue-42" def test_conflict_409(self, stub_server): @@ -198,7 +198,6 @@ def test_optional_params(self, stub_server): issue_number=1, repo="o/r", branch="egg/test", - mode="issue", prompt="do stuff", config={"key": "val"}, ) diff --git a/tests/sandbox/test_sdlc_cli.py b/tests/sandbox/test_sdlc_cli.py index a6d439bab7..7a248501f5 100644 --- a/tests/sandbox/test_sdlc_cli.py +++ b/tests/sandbox/test_sdlc_cli.py @@ -453,7 +453,6 @@ def test_concurrent_config_passed(self): issue_number=1, repo="owner/repo", branch="egg/issue-1", - mode="issue", network_mode="public", config={"concurrent_execution": True}, ) @@ -472,7 +471,6 @@ def test_no_concurrent_config(self): issue_number=2, repo="owner/repo", branch="egg/issue-2", - mode="issue", network_mode="private", config=None, ) diff --git a/tests/sandbox/test_sdlc_hitl.py b/tests/sandbox/test_sdlc_hitl.py index 2262699875..da86fbef58 100644 --- a/tests/sandbox/test_sdlc_hitl.py +++ b/tests/sandbox/test_sdlc_hitl.py @@ -82,29 +82,29 @@ def test_case_insensitive(self): class TestGetDraftPath: def test_refine_issue_mode(self): - path = _get_draft_path("refine", "issue", issue_number=42) + path = _get_draft_path("refine", issue_number=42) assert path == ".egg-state/drafts/42-analysis.md" - def test_refine_local_mode(self): - path = _get_draft_path("refine", "local", pipeline_id="local-abc") + def test_refine_with_pipeline_id(self): + path = _get_draft_path("refine", pipeline_id="local-abc") assert path == ".egg-state/drafts/local-abc-analysis.md" def test_implement_returns_none(self): - path = _get_draft_path("implement", "issue", issue_number=1) + path = _get_draft_path("implement", issue_number=1) assert path is None def test_plan_phase(self): - path = _get_draft_path("plan", "issue", issue_number=10) + path = _get_draft_path("plan", issue_number=10) assert path == ".egg-state/drafts/10-plan.md" def test_pr_phase(self): - path = _get_draft_path("pr", "issue", issue_number=5) + path = _get_draft_path("pr", issue_number=5) assert path == ".egg-state/drafts/5-pr.md" - def test_local_mode_fallback_prefix(self): - """When no pipeline_id given in local mode, prefix is 'local'.""" - path = _get_draft_path("refine", "local") - assert path == ".egg-state/drafts/local-analysis.md" + def test_fallback_prefix(self): + """When neither issue_number nor pipeline_id given, prefix is 'unknown'.""" + path = _get_draft_path("refine") + assert path == ".egg-state/drafts/unknown-analysis.md" # --------------------------------------------------------------------------- @@ -597,7 +597,7 @@ def test_explicit_phase_overrides_detect( handle_hitl_checkpoint(client, "issue-1", decision, pipeline_mode="issue", issue_number=1) # _get_draft_path should have been called with "plan", not "refine" - mock_draft_path.assert_called_once_with("plan", "issue", 1, "issue-1") + mock_draft_path.assert_called_once_with("plan", issue_number=1, pipeline_id="issue-1") @patch("egg_lib.sdlc_hitl._find_repo_path") @patch("egg_lib.sdlc_hitl._get_draft_path") @@ -620,7 +620,7 @@ def test_missing_phase_falls_back_to_detect( handle_hitl_checkpoint(client, "issue-1", decision, pipeline_mode="issue", issue_number=1) # _detect_phase should resolve "refine" from the question text - mock_draft_path.assert_called_once_with("refine", "issue", 1, "issue-1") + mock_draft_path.assert_called_once_with("refine", issue_number=1, pipeline_id="issue-1") @patch("egg_lib.sdlc_hitl._find_repo_path") @patch("egg_lib.sdlc_hitl._get_draft_path") @@ -644,7 +644,7 @@ def test_none_phase_falls_back_to_detect( handle_hitl_checkpoint(client, "issue-1", decision, pipeline_mode="issue", issue_number=1) # _detect_phase should resolve "implement" from the question text - mock_draft_path.assert_called_once_with("implement", "issue", 1, "issue-1") + mock_draft_path.assert_called_once_with("implement", issue_number=1, pipeline_id="issue-1") @patch("egg_lib.sdlc_hitl._find_repo_path") @patch("egg_lib.sdlc_hitl._get_draft_path") @@ -671,7 +671,7 @@ def test_unknown_phase_fetches_from_pipeline_api( handle_hitl_checkpoint(client, "issue-1", decision, pipeline_mode="issue", issue_number=1) client.get_pipeline.assert_called_once_with("issue-1") - mock_draft_path.assert_called_once_with("plan", "issue", 1, "issue-1") + mock_draft_path.assert_called_once_with("plan", issue_number=1, pipeline_id="issue-1") @patch("egg_lib.sdlc_hitl._find_repo_path") @patch("egg_lib.sdlc_hitl._get_draft_path") @@ -698,7 +698,7 @@ def test_unknown_phase_api_failure_stays_unknown( client.get_pipeline.assert_called_once_with("issue-1") # Phase stays "unknown" since API failed - mock_draft_path.assert_called_once_with("unknown", "issue", 1, "issue-1") + mock_draft_path.assert_called_once_with("unknown", issue_number=1, pipeline_id="issue-1") # --------------------------------------------------------------------------- @@ -2187,7 +2187,7 @@ def test_no_decision_type_no_options_falls_to_generic(self, mock_input, mock_rep class TestContractDecisionBridge: - """Tests for the contract decision bridge (local-mode HITL).""" + """Tests for the contract decision bridge (HITL).""" def _write_contract(self, tmp_path, key, decisions): """Helper: write a contract JSON with the given decisions list.""" @@ -2204,21 +2204,21 @@ def _write_contract(self, tmp_path, key, decisions): # -- _get_contract_key -- - def test_get_contract_key_issue_mode(self): - """Issue mode returns str(issue_number).""" - assert _get_contract_key("issue", issue_number=42) == "42" + def test_get_contract_key_with_issue_number(self): + """Returns str(issue_number) when provided.""" + assert _get_contract_key(issue_number=42) == "42" - def test_get_contract_key_issue_mode_no_number(self): - """Issue mode with no issue_number returns None.""" - assert _get_contract_key("issue") is None + def test_get_contract_key_no_identifiers(self): + """Returns None when no identifiers provided.""" + assert _get_contract_key() is None - def test_get_contract_key_local_mode(self): - """Local mode returns pipeline_id.""" - assert _get_contract_key("local", pipeline_id="my-pipeline") == "my-pipeline" + def test_get_contract_key_with_pipeline_id(self): + """Returns pipeline_id when provided.""" + assert _get_contract_key(pipeline_id="my-pipeline") == "my-pipeline" - def test_get_contract_key_local_mode_no_pipeline(self): - """Local mode with no pipeline_id returns None.""" - assert _get_contract_key("local") is None + def test_get_contract_key_pipeline_id_no_value(self): + """Returns None when pipeline_id is empty.""" + assert _get_contract_key(pipeline_id="") is None # -- _load_pending_contract_decisions -- diff --git a/uv.lock b/uv.lock index 64cf92c189..d60dec59f7 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.11" +requires-python = ">=3.13" [[package]] name = "annotated-doc" @@ -26,7 +26,6 @@ version = "4.12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } wheels = [ @@ -84,31 +83,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, @@ -160,38 +134,6 @@ version = "3.4.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, @@ -254,32 +196,6 @@ version = "7.13.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/01/abca50583a8975bb6e1c59eff67ed8e48bb127c07dad5c28d9e96ccc09ec/coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e", size = 218971, upload-time = "2026-01-25T12:57:36.953Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0e/b6489f344d99cd1e5b4d5e1be52dfd3f8a3dc5112aa6c33948da8cabad4e/coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e", size = 219473, upload-time = "2026-01-25T12:57:38.934Z" }, - { url = "https://files.pythonhosted.org/packages/17/11/db2f414915a8e4ec53f60b17956c27f21fb68fcf20f8a455ce7c2ccec638/coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508", size = 249896, upload-time = "2026-01-25T12:57:40.365Z" }, - { url = "https://files.pythonhosted.org/packages/80/06/0823fe93913663c017e508e8810c998c8ebd3ec2a5a85d2c3754297bdede/coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b", size = 251810, upload-time = "2026-01-25T12:57:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/61/dc/b151c3cc41b28cdf7f0166c5fa1271cbc305a8ec0124cce4b04f74791a18/coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b", size = 253920, upload-time = "2026-01-25T12:57:44.026Z" }, - { url = "https://files.pythonhosted.org/packages/2d/35/e83de0556e54a4729a2b94ea816f74ce08732e81945024adee46851c2264/coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f", size = 250025, upload-time = "2026-01-25T12:57:45.624Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/af2eb9c3926ce3ea0d58a0d2516fcbdacf7a9fc9559fe63076beaf3f2596/coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3", size = 251612, upload-time = "2026-01-25T12:57:47.713Z" }, - { url = "https://files.pythonhosted.org/packages/26/62/5be2e25f3d6c711d23b71296f8b44c978d4c8b4e5b26871abfc164297502/coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b", size = 249670, upload-time = "2026-01-25T12:57:49.378Z" }, - { url = "https://files.pythonhosted.org/packages/b3/51/400d1b09a8344199f9b6a6fc1868005d766b7ea95e7882e494fa862ca69c/coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1", size = 249395, upload-time = "2026-01-25T12:57:50.86Z" }, - { url = "https://files.pythonhosted.org/packages/e0/36/f02234bc6e5230e2f0a63fd125d0a2093c73ef20fdf681c7af62a140e4e7/coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059", size = 250298, upload-time = "2026-01-25T12:57:52.287Z" }, - { url = "https://files.pythonhosted.org/packages/b0/06/713110d3dd3151b93611c9cbfc65c15b4156b44f927fced49ac0b20b32a4/coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031", size = 221485, upload-time = "2026-01-25T12:57:53.876Z" }, - { url = "https://files.pythonhosted.org/packages/16/0c/3ae6255fa1ebcb7dec19c9a59e85ef5f34566d1265c70af5b2fc981da834/coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e", size = 222421, upload-time = "2026-01-25T12:57:55.433Z" }, - { url = "https://files.pythonhosted.org/packages/b5/37/fabc3179af4d61d89ea47bd04333fec735cd5e8b59baad44fed9fc4170d7/coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28", size = 221088, upload-time = "2026-01-25T12:57:57.41Z" }, - { url = "https://files.pythonhosted.org/packages/46/39/e92a35f7800222d3f7b2cbb7bbc3b65672ae8d501cb31801b2d2bd7acdf1/coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d", size = 219142, upload-time = "2026-01-25T12:58:00.448Z" }, - { url = "https://files.pythonhosted.org/packages/45/7a/8bf9e9309c4c996e65c52a7c5a112707ecdd9fbaf49e10b5a705a402bbb4/coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3", size = 219503, upload-time = "2026-01-25T12:58:02.451Z" }, - { url = "https://files.pythonhosted.org/packages/87/93/17661e06b7b37580923f3f12406ac91d78aeed293fb6da0b69cc7957582f/coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99", size = 251006, upload-time = "2026-01-25T12:58:04.059Z" }, - { url = "https://files.pythonhosted.org/packages/12/f0/f9e59fb8c310171497f379e25db060abef9fa605e09d63157eebec102676/coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f", size = 253750, upload-time = "2026-01-25T12:58:05.574Z" }, - { url = "https://files.pythonhosted.org/packages/e5/b1/1935e31add2232663cf7edd8269548b122a7d100047ff93475dbaaae673e/coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f", size = 254862, upload-time = "2026-01-25T12:58:07.647Z" }, - { url = "https://files.pythonhosted.org/packages/af/59/b5e97071ec13df5f45da2b3391b6cdbec78ba20757bc92580a5b3d5fa53c/coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa", size = 251420, upload-time = "2026-01-25T12:58:09.309Z" }, - { url = "https://files.pythonhosted.org/packages/3f/75/9495932f87469d013dc515fb0ce1aac5fa97766f38f6b1a1deb1ee7b7f3a/coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce", size = 252786, upload-time = "2026-01-25T12:58:10.909Z" }, - { url = "https://files.pythonhosted.org/packages/6a/59/af550721f0eb62f46f7b8cb7e6f1860592189267b1c411a4e3a057caacee/coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94", size = 250928, upload-time = "2026-01-25T12:58:12.449Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b1/21b4445709aae500be4ab43bbcfb4e53dc0811c3396dcb11bf9f23fd0226/coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5", size = 250496, upload-time = "2026-01-25T12:58:14.047Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b1/0f5d89dfe0392990e4f3980adbde3eb34885bc1effb2dc369e0bf385e389/coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b", size = 252373, upload-time = "2026-01-25T12:58:15.976Z" }, - { url = "https://files.pythonhosted.org/packages/01/c9/0cf1a6a57a9968cc049a6b896693faa523c638a5314b1fc374eb2b2ac904/coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41", size = 221696, upload-time = "2026-01-25T12:58:17.517Z" }, - { url = "https://files.pythonhosted.org/packages/4d/05/d7540bf983f09d32803911afed135524570f8c47bb394bf6206c1dc3a786/coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e", size = 222504, upload-time = "2026-01-25T12:58:19.115Z" }, - { url = "https://files.pythonhosted.org/packages/15/8b/1a9f037a736ced0a12aacf6330cdaad5008081142a7070bc58b0f7930cbc/coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894", size = 221120, upload-time = "2026-01-25T12:58:21.334Z" }, { url = "https://files.pythonhosted.org/packages/a7/f0/3d3eac7568ab6096ff23791a526b0048a1ff3f49d0e236b2af6fb6558e88/coverage-7.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed75de7d1217cf3b99365d110975f83af0528c849ef5180a12fd91b5064df9d6", size = 219168, upload-time = "2026-01-25T12:58:23.376Z" }, { url = "https://files.pythonhosted.org/packages/a3/a6/f8b5cfeddbab95fdef4dcd682d82e5dcff7a112ced57a959f89537ee9995/coverage-7.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97e596de8fa9bada4d88fde64a3f4d37f1b6131e4faa32bad7808abc79887ddc", size = 219537, upload-time = "2026-01-25T12:58:24.932Z" }, { url = "https://files.pythonhosted.org/packages/7b/e6/8d8e6e0c516c838229d1e41cadcec91745f4b1031d4db17ce0043a0423b4/coverage-7.13.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:68c86173562ed4413345410c9480a8d64864ac5e54a5cda236748031e094229f", size = 250528, upload-time = "2026-01-25T12:58:26.567Z" }, @@ -335,11 +251,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/db/d291e30fdf7ea617a335531e72294e0c723356d7fdde8fba00610a76bda9/coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5", size = 210943, upload-time = "2026-01-25T13:00:02.388Z" }, ] -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version <= '3.11'" }, -] - [[package]] name = "cryptography" version = "43.0.3" @@ -617,28 +528,6 @@ version = "0.7.8" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e7/24/5f3646ff414285e0f7708fa4e946b9bf538345a41d1c375c439467721a5e/librt-0.7.8.tar.gz", hash = "sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862", size = 148323, upload-time = "2026-01-14T12:56:16.876Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/a3/87ea9c1049f2c781177496ebee29430e4631f439b8553a4969c88747d5d8/librt-0.7.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3e9c11aa260c31493d4b3197d1e28dd07768594a4f92bec4506849d736248f", size = 56507, upload-time = "2026-01-14T12:54:54.156Z" }, - { url = "https://files.pythonhosted.org/packages/5e/4a/23bcef149f37f771ad30203d561fcfd45b02bc54947b91f7a9ac34815747/librt-0.7.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddb52499d0b3ed4aa88746aaf6f36a08314677d5c346234c3987ddc506404eac", size = 58455, upload-time = "2026-01-14T12:54:55.978Z" }, - { url = "https://files.pythonhosted.org/packages/22/6e/46eb9b85c1b9761e0f42b6e6311e1cc544843ac897457062b9d5d0b21df4/librt-0.7.8-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e9c0afebbe6ce177ae8edba0c7c4d626f2a0fc12c33bb993d163817c41a7a05c", size = 164956, upload-time = "2026-01-14T12:54:57.311Z" }, - { url = "https://files.pythonhosted.org/packages/7a/3f/aa7c7f6829fb83989feb7ba9aa11c662b34b4bd4bd5b262f2876ba3db58d/librt-0.7.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:631599598e2c76ded400c0a8722dec09217c89ff64dc54b060f598ed68e7d2a8", size = 174364, upload-time = "2026-01-14T12:54:59.089Z" }, - { url = "https://files.pythonhosted.org/packages/3f/2d/d57d154b40b11f2cb851c4df0d4c4456bacd9b1ccc4ecb593ddec56c1a8b/librt-0.7.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c1ba843ae20db09b9d5c80475376168feb2640ce91cd9906414f23cc267a1ff", size = 188034, upload-time = "2026-01-14T12:55:00.141Z" }, - { url = "https://files.pythonhosted.org/packages/59/f9/36c4dad00925c16cd69d744b87f7001792691857d3b79187e7a673e812fb/librt-0.7.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b5b007bb22ea4b255d3ee39dfd06d12534de2fcc3438567d9f48cdaf67ae1ae3", size = 186295, upload-time = "2026-01-14T12:55:01.303Z" }, - { url = "https://files.pythonhosted.org/packages/23/9b/8a9889d3df5efb67695a67785028ccd58e661c3018237b73ad081691d0cb/librt-0.7.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbd79caaf77a3f590cbe32dc2447f718772d6eea59656a7dcb9311161b10fa75", size = 181470, upload-time = "2026-01-14T12:55:02.492Z" }, - { url = "https://files.pythonhosted.org/packages/43/64/54d6ef11afca01fef8af78c230726a9394759f2addfbf7afc5e3cc032a45/librt-0.7.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:87808a8d1e0bd62a01cafc41f0fd6818b5a5d0ca0d8a55326a81643cdda8f873", size = 201713, upload-time = "2026-01-14T12:55:03.919Z" }, - { url = "https://files.pythonhosted.org/packages/2d/29/73e7ed2991330b28919387656f54109139b49e19cd72902f466bd44415fd/librt-0.7.8-cp311-cp311-win32.whl", hash = "sha256:31724b93baa91512bd0a376e7cf0b59d8b631ee17923b1218a65456fa9bda2e7", size = 43803, upload-time = "2026-01-14T12:55:04.996Z" }, - { url = "https://files.pythonhosted.org/packages/3f/de/66766ff48ed02b4d78deea30392ae200bcbd99ae61ba2418b49fd50a4831/librt-0.7.8-cp311-cp311-win_amd64.whl", hash = "sha256:978e8b5f13e52cf23a9e80f3286d7546baa70bc4ef35b51d97a709d0b28e537c", size = 50080, upload-time = "2026-01-14T12:55:06.489Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e3/33450438ff3a8c581d4ed7f798a70b07c3206d298cf0b87d3806e72e3ed8/librt-0.7.8-cp311-cp311-win_arm64.whl", hash = "sha256:20e3946863d872f7cabf7f77c6c9d370b8b3d74333d3a32471c50d3a86c0a232", size = 43383, upload-time = "2026-01-14T12:55:07.49Z" }, - { url = "https://files.pythonhosted.org/packages/56/04/79d8fcb43cae376c7adbab7b2b9f65e48432c9eced62ac96703bcc16e09b/librt-0.7.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b6943885b2d49c48d0cff23b16be830ba46b0152d98f62de49e735c6e655a63", size = 57472, upload-time = "2026-01-14T12:55:08.528Z" }, - { url = "https://files.pythonhosted.org/packages/b4/ba/60b96e93043d3d659da91752689023a73981336446ae82078cddf706249e/librt-0.7.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46ef1f4b9b6cc364b11eea0ecc0897314447a66029ee1e55859acb3dd8757c93", size = 58986, upload-time = "2026-01-14T12:55:09.466Z" }, - { url = "https://files.pythonhosted.org/packages/7c/26/5215e4cdcc26e7be7eee21955a7e13cbf1f6d7d7311461a6014544596fac/librt-0.7.8-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:907ad09cfab21e3c86e8f1f87858f7049d1097f77196959c033612f532b4e592", size = 168422, upload-time = "2026-01-14T12:55:10.499Z" }, - { url = "https://files.pythonhosted.org/packages/0f/84/e8d1bc86fa0159bfc24f3d798d92cafd3897e84c7fea7fe61b3220915d76/librt-0.7.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2991b6c3775383752b3ca0204842743256f3ad3deeb1d0adc227d56b78a9a850", size = 177478, upload-time = "2026-01-14T12:55:11.577Z" }, - { url = "https://files.pythonhosted.org/packages/57/11/d0268c4b94717a18aa91df1100e767b010f87b7ae444dafaa5a2d80f33a6/librt-0.7.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03679b9856932b8c8f674e87aa3c55ea11c9274301f76ae8dc4d281bda55cf62", size = 192439, upload-time = "2026-01-14T12:55:12.7Z" }, - { url = "https://files.pythonhosted.org/packages/8d/56/1e8e833b95fe684f80f8894ae4d8b7d36acc9203e60478fcae599120a975/librt-0.7.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3968762fec1b2ad34ce57458b6de25dbb4142713e9ca6279a0d352fa4e9f452b", size = 191483, upload-time = "2026-01-14T12:55:13.838Z" }, - { url = "https://files.pythonhosted.org/packages/17/48/f11cf28a2cb6c31f282009e2208312aa84a5ee2732859f7856ee306176d5/librt-0.7.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bb7a7807523a31f03061288cc4ffc065d684c39db7644c676b47d89553c0d714", size = 185376, upload-time = "2026-01-14T12:55:15.017Z" }, - { url = "https://files.pythonhosted.org/packages/b8/6a/d7c116c6da561b9155b184354a60a3d5cdbf08fc7f3678d09c95679d13d9/librt-0.7.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad64a14b1e56e702e19b24aae108f18ad1bf7777f3af5fcd39f87d0c5a814449", size = 206234, upload-time = "2026-01-14T12:55:16.571Z" }, - { url = "https://files.pythonhosted.org/packages/61/de/1975200bb0285fc921c5981d9978ce6ce11ae6d797df815add94a5a848a3/librt-0.7.8-cp312-cp312-win32.whl", hash = "sha256:0241a6ed65e6666236ea78203a73d800dbed896cf12ae25d026d75dc1fcd1dac", size = 44057, upload-time = "2026-01-14T12:55:18.077Z" }, - { url = "https://files.pythonhosted.org/packages/8e/cd/724f2d0b3461426730d4877754b65d39f06a41ac9d0a92d5c6840f72b9ae/librt-0.7.8-cp312-cp312-win_amd64.whl", hash = "sha256:6db5faf064b5bab9675c32a873436b31e01d66ca6984c6f7f92621656033a708", size = 50293, upload-time = "2026-01-14T12:55:19.179Z" }, - { url = "https://files.pythonhosted.org/packages/bd/cf/7e899acd9ee5727ad8160fdcc9994954e79fab371c66535c60e13b968ffc/librt-0.7.8-cp312-cp312-win_arm64.whl", hash = "sha256:57175aa93f804d2c08d2edb7213e09276bd49097611aefc37e3fa38d1fb99ad0", size = 43574, upload-time = "2026-01-14T12:55:20.185Z" }, { url = "https://files.pythonhosted.org/packages/a1/fe/b1f9de2829cf7fc7649c1dcd202cfd873837c5cc2fc9e526b0e7f716c3d2/librt-0.7.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c3995abbbb60b3c129490fa985dfe6cac11d88fc3c36eeb4fb1449efbbb04fc", size = 57500, upload-time = "2026-01-14T12:55:21.219Z" }, { url = "https://files.pythonhosted.org/packages/eb/d4/4a60fbe2e53b825f5d9a77325071d61cd8af8506255067bf0c8527530745/librt-0.7.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:44e0c2cbc9bebd074cf2cdbe472ca185e824be4e74b1c63a8e934cea674bebf2", size = 59019, upload-time = "2026-01-14T12:55:22.256Z" }, { url = "https://files.pythonhosted.org/packages/6a/37/61ff80341ba5159afa524445f2d984c30e2821f31f7c73cf166dcafa5564/librt-0.7.8-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d2f1e492cae964b3463a03dc77a7fe8742f7855d7258c7643f0ee32b6651dd3", size = 169015, upload-time = "2026-01-14T12:55:23.24Z" }, @@ -692,28 +581,6 @@ version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, - { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, - { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, - { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, - { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, @@ -812,18 +679,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, - { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, - { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, - { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, - { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, - { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, - { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, - { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, @@ -942,34 +797,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, @@ -1012,22 +839,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, ] [[package]] @@ -1088,7 +899,7 @@ name = "pytest-cov" version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coverage", extra = ["toml"] }, + { name = "coverage" }, { name = "pluggy" }, { name = "pytest" }, ] @@ -1132,12 +943,6 @@ name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, - { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, - { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, @@ -1152,25 +957,6 @@ version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, @@ -1208,7 +994,6 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "rpds-py" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ @@ -1249,36 +1034,6 @@ version = "0.30.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, - { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, - { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, - { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, - { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, - { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, - { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, - { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, - { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, - { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, - { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, - { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, @@ -1337,18 +1092,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, - { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, - { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, - { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, - { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, - { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, - { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, - { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, - { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, - { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, - { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, ] [[package]] @@ -1414,7 +1157,6 @@ version = "0.52.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } wheels = [ @@ -1430,60 +1172,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl", hash = "sha256:4a36dccefd7aeea0c70135526cecb7766c4c84c473b1af68db23d541b6dc1820", size = 54428, upload-time = "2025-11-20T10:06:05.946Z" }, ] -[[package]] -name = "tomli" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, - { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, - { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, - { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, - { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, - { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, - { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, - { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, - { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, - { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, - { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, - { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, - { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, - { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, - { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, - { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, - { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, - { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, - { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, - { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, - { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, - { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, - { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, - { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, - { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, - { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, -] - [[package]] name = "typer" version = "0.24.1"