diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 6501628..6b5755b 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -34,6 +34,24 @@ jobs: --root "$RUNNER_TEMP/borg-source" \ --allowlist "$RUNNER_TEMP/borg-source/RELEASE-ALLOWLIST.json" \ --inventory "$RUNNER_TEMP/borg-source/RELEASE-INVENTORY.json" + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: '3.12' + - name: Install hash-pinned connector test dependencies + shell: bash + run: | + python3 -m venv "$RUNNER_TEMP/borg-connector-tests" + "$RUNNER_TEMP/borg-connector-tests/bin/python" -m pip install \ + --require-hashes -r connector/requirements-test.lock + - name: Test connector trust boundaries and compatibility + working-directory: connector + shell: bash + run: | + "$RUNNER_TEMP/borg-connector-tests/bin/python" -B -m unittest -v \ + test_recall_policy test_operation_diagnostics test_job_portability test_fleet \ + test_connector.ConnectorTests test_capabilities test_concurrency \ + test_resilience.ReceiptTests test_resilience.MiddlewareReceiptTests \ + test_resilience.LaneTests - name: Verify release, adapter and installer guards run: python3 -B -m unittest installer.test_release_guard installer.test_adapters installer.test_startup_preflight - name: Install pinned native Codex runtime diff --git a/RELEASE-INVENTORY.json b/RELEASE-INVENTORY.json index 55e228a..f4027f6 100644 --- a/RELEASE-INVENTORY.json +++ b/RELEASE-INVENTORY.json @@ -1,6 +1,6 @@ { "excluded_self": "RELEASE-INVENTORY.json", - "file_count": 372, + "file_count": 379, "files": [ { "bytes": 1914, @@ -8,9 +8,9 @@ "sha256": "6bfdccbe0a3994dba7c402c2f7a2e0713de367e92476c68f680bc87439e7d1cf" }, { - "bytes": 1756, + "bytes": 2686, "path": ".github/workflows/verify.yml", - "sha256": "d97bb88a2b4944ebdaa16b70322c11f1ac568c35d9321cdb9ed133fa4573e38b" + "sha256": "155639c36e00251bd6979c6a86d969078e44bc65bbb3be6001b36c51a9c43535" }, { "bytes": 29, @@ -273,9 +273,9 @@ "sha256": "1600e1b9bb2eda4fad5790d5ef17bb17d2e868439b58fceebcad09e1af3c071d" }, { - "bytes": 5983, + "bytes": 9208, "path": "connector/README.md", - "sha256": "16cc2db889c1afa8a5c9a9b3946bd9c814f44bb6c90612479e65b7017b8c2581" + "sha256": "c6bb52f9a9ff4d28589266034140fe56a41570b679f50ec773f8569c07fa16ec" }, { "bytes": 791, @@ -288,9 +288,9 @@ "sha256": "5dbd10ab2ca3ac830d79b2fc16455174496cc0d04e20e4911eeddff8073aa087" }, { - "bytes": 30579, + "bytes": 31144, "path": "connector/borg_context_server.py", - "sha256": "1da7e9bab3f2dfcb73de8ce2f990c426142b5491c47078bbaf2335ab007655c2" + "sha256": "3133dd39aea9f15575ea8588ef04577d5cbd8e3a5e21969101bcbd14536cc58f" }, { "bytes": 7861, @@ -308,9 +308,9 @@ "sha256": "c2d180488c05184b44efeda60255142a605ebe9e1287de8f83d0ad46c4fb0b7a" }, { - "bytes": 39887, + "bytes": 41715, "path": "connector/computer_tools.py", - "sha256": "1a9d13e51839f275f8ffb45b012b878ba114474da9386542146eb8175927bc4c" + "sha256": "14cc0e44b8fbde5a60b5ec9a6fa766daba7217ded66001d9f444cc45e3a52c0e" }, { "bytes": 9415, @@ -333,14 +333,14 @@ "sha256": "b47e976b390b5867fce5b88f5e23367d934cc6271c1b64f36087a19311ea79f4" }, { - "bytes": 13799, + "bytes": 14901, "path": "connector/fleet_tools.py", - "sha256": "930e947ad63c7439f5fdd0cf8d90bd6d73445e35303e6fb0b06800a133f70d28" + "sha256": "c900c72a65a08367aa01e07d4b7fda08529b7384ba4b933151dcf65ce42c44f6" }, { - "bytes": 16169, + "bytes": 16583, "path": "connector/job_tools.py", - "sha256": "862d0c1398d88cf0219c33c1650a006d8309c16daff984d0de2bc3320ab4e137" + "sha256": "7c6995becd1557ef39a0746bf8667133dab33a2e4c2dc89f3195ea75a1066642" }, { "bytes": 18999, @@ -353,9 +353,14 @@ "sha256": "c7d89ef33773211c7f01921a6c158b89edd7235b86e82e648898bc2170fd4d1d" }, { - "bytes": 6792, + "bytes": 3290, + "path": "connector/operation_diagnostics.py", + "sha256": "c97aebf123811cb14f6c139ab780db8a0fd16dbb95dedb712a93f9b0e999ef2c" + }, + { + "bytes": 7484, "path": "connector/operation_receipts.py", - "sha256": "49aed0c2e7aab70017a2b8a5d2ebfbd88b89ff6df286a55e77519f89be0cb495" + "sha256": "7f6e05ce4f125a72f3e5edacfc13a81e538d7dddeac557d112546f7504afd012" }, { "bytes": 7463, @@ -367,6 +372,11 @@ "path": "connector/process_resources.py", "sha256": "fc5589a3692aa250ef6d6690746579f009f98b62ac040b546829b2b85f67e58d" }, + { + "bytes": 4756, + "path": "connector/recall_policy.py", + "sha256": "93b93096adc1b34ec8992c8a2e28f43976a81a51b68f15d3fba7d4b13b3563da" + }, { "bytes": 7218, "path": "connector/remote_tools.py", @@ -377,6 +387,16 @@ "path": "connector/repository_inspection.py", "sha256": "ba46577ba89fbc0e1fd9c1964a777474d6d48e3af3b0135e5ab126dbb3b27068" }, + { + "bytes": 122, + "path": "connector/requirements-test.in", + "sha256": "01bf67ce81dae17a77b7bffd0ac5ea88b6ec9c2956d862ee2db607ac669d2566" + }, + { + "bytes": 79264, + "path": "connector/requirements-test.lock", + "sha256": "fbddfa7ef0926ac429ec127444a71ff4b72485459b5930f86465ceec1c56581b" + }, { "bytes": 1008, "path": "connector/runtime_paths.py", @@ -423,9 +443,9 @@ "sha256": "0894b284dd34af6495190116a4b7670bede640225b81155278061eabccedaf7b" }, { - "bytes": 15695, + "bytes": 16225, "path": "connector/test_connector.py", - "sha256": "f88be7ca2e80c2072c8b968551438c054e1d940b464fc5296e45c4ffa94fa82f" + "sha256": "ad52ce4b14301a57bde1f248011fd04388c1f9108ca589078d978613e0089674" }, { "bytes": 5688, @@ -457,6 +477,16 @@ "path": "connector/test_independent_settings.py", "sha256": "9dfe7b26bec7360fe0305dc9708e7ada6ce3109964a040f05e3a914d9be73948" }, + { + "bytes": 2828, + "path": "connector/test_job_portability.py", + "sha256": "b3f8a1e0d83367329319ee3e2b0dde4b32fbdab2bca8580dc56d17d681f1aca8" + }, + { + "bytes": 15122, + "path": "connector/test_operation_diagnostics.py", + "sha256": "89d60593f362e984ed4f3e7b9610c55e94a73c7c81b7152a6093f2d1d11750b7" + }, { "bytes": 8806, "path": "connector/test_process_handoff.py", @@ -472,15 +502,20 @@ "path": "connector/test_process_resources.py", "sha256": "ef9908cf64b3729c7ea3653f50ac19ba555567c9997a5ac1bdb01803a6739283" }, + { + "bytes": 9640, + "path": "connector/test_recall_policy.py", + "sha256": "5c8cdc792a06b08c6e888004f8bc07d472ace2ba32b330d8395354dda3dd9b3f" + }, { "bytes": 8418, "path": "connector/test_repository_inspection.py", "sha256": "af93c743bc29f59ab44798acebd6e6429b730a48e2e958fa8757af395e2f5406" }, { - "bytes": 14511, + "bytes": 14542, "path": "connector/test_resilience.py", - "sha256": "865b2f3bc8b45ac43cfc322c49a45835965240f9c7903a1c6996e98324894af4" + "sha256": "41e95dc345e125b839b186bf8f59d2fd5a0ea642195284a54913b9eaf9240ff1" }, { "bytes": 23, @@ -1863,7 +1898,7 @@ "sha256": "cd4bdb4529012e0cfcd38e059215f9ea433b8ee1fa636276b36c6515e7949e28" } ], - "inventory_sha256": "787241ae9bf10d37224ad69423f5b37cbc3d8110df4c4e7d9461320adf1afbe6", + "inventory_sha256": "1233b085a870bd6a7e9ada3a9df14bb73f9cb20ecb70f4769cf0b876a331c405", "schema": "borg-public-inventory/v1", - "total_bytes": 87734443 + "total_bytes": 87858782 } diff --git a/connector/README.md b/connector/README.md index 67dc784..c86acc8 100644 --- a/connector/README.md +++ b/connector/README.md @@ -84,3 +84,54 @@ a bounded migration mechanism, not indefinite process-history storage. Configuration and service commands are described in [INSTALL.md](../docs/INSTALL.md). For an owner's Cloudflare/ChatGPT connection, follow [WEB.md](../docs/WEB.md). Optional SSH and credential registries start empty and must be configured for that owner. + +## Recall result selection + +`borg_search` and `borg_project_context` share a read-only selection policy. The +existing response fields and call arguments are retained. Both now report +`result_floor` (`populated`, `sparse`, or, for degraded project context, +`unavailable`) and a versioned `retrieval` object with candidate/returned counts, +omission reasons, and the policy used. An upstream outage is not a successful +empty search. All returned memories remain **candidate context**, not authority. + +Semantic queries preserve upstream ordering and valid low-scoring candidates: +a similarity score is not a confidence probability. Double-quoted names or +phrases require literal, whitespace-normalized matches. Opaque identifiers require +matching record text or the exact native row ID. For example, `"Ada Example" role` +does not fill the result with other people sharing only one name. Unquoted names +remain semantic searches; this is not a replacement for entity resolution. +Literal matches do not prove a claim is true, current, or about a unique entity. + +Only a bounded upstream pool is inspected (at most 25 candidates). A sparse +response therefore means no additional eligible candidates in that pool, not +proof that the entire corpus lacks the requested fact. A small, whole-record +vacuity rule withholds tautological tool statements; discussion of those strings +is retained. Nothing is deleted, quarantined in storage, or promoted to a higher +trust tier. The policy performs no additional model calls or memory writes. + +## Durable effect and failure diagnostics + +New operation receipts use version 2 and retain existing fields. Their bounded +`diagnostics` object records execution phase, effect state, typed cause, and, +for fleet calls, target host/tool, verified instance/generation and target receipt +when available. Credential values, home paths, arguments, error text and result +payloads are not stored in this object. Existing version-1 receipts remain readable. +`borg_operation_status` returns the same durable evidence after the request ends. + +A post-dispatch error, including a permission or rate-limit error, is +`outcome_unknown` unless absence of execution is established by local preflight. +A timeout before dispatch is `not_started`; a lost response after dispatch is not. +`retryable` is true only for a known transient failure with `not_started` effects; +it is advice to recheck the target and admission, never an automatic retry. +`retry_without_change` remains false. Running receipts are provisional, and +interrupted receipts recover to unknown rather than authorizing a repeat. +Successful receipts prove completion of the native tool call, not deployment or +an external business outcome. Finalized receipts cannot regress after cancellation. + +The connector trust and compatibility suites run on both CI operating systems +using `requirements-test.lock`, hash-pinned against the accepted runtime versions. + +Durable jobs preserve `/bin/zsh -lc` where that executable exists. On POSIX +hosts without executable zsh they use `/bin/sh -c`. If neither shell is +available, launch is refused before creating job artifacts. The fallback and +its durable stdout/exit-status behavior are covered by cross-platform CI. diff --git a/connector/borg_context_server.py b/connector/borg_context_server.py index 7ea19d1..3db9dbe 100644 --- a/connector/borg_context_server.py +++ b/connector/borg_context_server.py @@ -224,6 +224,7 @@ async def mem0_call(client: Client, tool: str, args: dict[str, Any]) -> Any: from repository_inspection import _repos_matching, _repo_snapshot, _needles +from recall_policy import select_memories, unavailable_retrieval def _memory_summary(rows: Any) -> list[dict[str, Any]]: @@ -360,7 +361,7 @@ async def borg_status(self) -> dict[str, Any]: "ui_tools_configured": bool(ui), "credential_handles_configured": bool(credentials), "inbox_controller_configured": bool(computer.get("inbox_client_config")), - "failure_diagnostics_version": 2, + "failure_diagnostics_version": 3, "readiness_notice": "Connector liveness, dependency readiness, freshness and completed-operation evidence are separate signals."}, "caller_boundary": "Dedicated local Bearer; the public gateway requires BORG owner OAuth independently of the ChatGPT account.", "graph": graph, "source_notice": NOTICE, "activity": _activity("", 0), @@ -380,13 +381,19 @@ async def borg_capabilities(self) -> dict[str, Any]: return build_manifest(self.settings, schemas) async def borg_search(self, query: str, limit: int = 6, include_graph: bool = True) -> dict[str, Any]: - """Search across the owner's entire BORG memory collection. Results are candidate context.""" + """Search owner memory as candidates, not proof. Quote names/phrases for literal matching. + Opaque identifiers also require an exact match. Sparse describes the candidate pool, + not corpus-wide absence; upstream failures remain errors. + """ if not query.strip() or len(query) > 2000: raise ToolError("Supply a nonempty query of at most 2000 characters") + requested = max(1, min(int(limit), 12)) async with self.upstream() as (client, _): rows = await mem0_call(client, "memory_search", { - "query": query, "limit": max(1, min(int(limit), 12)), "include_graph": include_graph}) - return {"query": query, "results": _memory_summary(rows), "source_notice": NOTICE} + "query": query, "limit": min(requested * 4, 25), "include_graph": include_graph}) + selected, selection = select_memories(query, rows, requested) + return {"query": query, "results": _memory_summary(selected), "source_notice": NOTICE, + **selection} async def borg_projects(self, query: str = "", limit: int = 12) -> dict[str, Any]: """Find repositories locally; tolerate outage but fail closed on identity mismatch.""" @@ -409,12 +416,16 @@ async def borg_project_context(self, project: str, task: str = "", limit: int = authorize() memories = [] memory_status = "UNAVAILABLE" + selection = unavailable_retrieval() + query = " ".join((project, task)).strip() + requested = max(1, min(int(limit), 12)) try: async with self.upstream() as (client, _): rows = await mem0_call(client, "memory_search", { - "query": " ".join((project, task)).strip(), "limit": max(1, min(int(limit), 12)), + "query": query, "limit": min(requested * 4, 25), "include_graph": True}) - memories = _memory_summary(rows) + selected, selection = select_memories(query, rows, requested) + memories = _memory_summary(selected) memory_status = "PASS" except ToolError as exc: if "identity or scope differs" in str(exc): @@ -424,7 +435,7 @@ async def borg_project_context(self, project: str, task: str = "", limit: int = repos = await asyncio.to_thread(_repos_matching, project, self.settings.project_roots, 6) return {"project": project, "task": task, "observed_at": _now(), "memory_notice": NOTICE, "memory_status": memory_status, "recalled_context": memories, "live_repositories": repos, - "activity": _activity(project, max(1, min(int(limit), 12)))} + "activity": _activity(project, requested), **selection} async def borg_remember(self, text: str, scope: str | None = None) -> dict[str, Any]: """Save one owner-requested durable fact in BORG. Never store secrets, client personal data or dollar amounts. diff --git a/connector/computer_tools.py b/connector/computer_tools.py index 38d1a06..bb7066d 100644 --- a/connector/computer_tools.py +++ b/connector/computer_tools.py @@ -26,6 +26,7 @@ from fastmcp.exceptions import ToolError from fastmcp.server.middleware import Middleware from mcp.types import ToolAnnotations +from operation_diagnostics import for_call LOG = logging.getLogger("borg.audit") DESCRIPTIONS = { @@ -66,6 +67,8 @@ def contains_secret(value: str) -> bool: FAILURE_MESSAGES = { + "bad_request": "The request is invalid. Correct it before retrying.", + "identity_mismatch": "The selected target identity does not match its enrollment. Verify the target; do not bypass identity checks.", "credential_withheld": "Credential material was withheld. Verify any preceding change before retrying.", "authentication_required": "Authentication or a user-owned sign-in step is required.", "busy": "BORG host capacity or the selected resource is busy. Check concurrency in borg_status; reconcile any uncertain write before retrying.", @@ -89,6 +92,16 @@ def classify_failure(value) -> str: text = type(value).__name__.casefold() if contains_secret(text) or "credential" in text or "secret" in text or "token material" in text: return "credential_withheld" + if isinstance(value, TimeoutError): + return "timeout" + if isinstance(value, ConnectionError): + return "provider_unavailable" + if isinstance(value, PermissionError): + return "permission_denied" + if "borg_bad_request" in text: + return "bad_request" + if "borg_identity_mismatch" in text: + return "identity_mismatch" if any(term in text for term in ("too many open files", "borg_resource_exhausted")): return "resource_exhausted" if "borg_busy" in text or "capability is busy" in text or "queue is full" in text: @@ -114,7 +127,7 @@ def classify_failure(value) -> str: def failure_meta(code: str) -> dict: return { - "version": 2, + "version": 3, "code": code, "message": FAILURE_MESSAGES.get(code, FAILURE_MESSAGES["downstream_error"]), "retry_without_change": False, @@ -250,60 +263,87 @@ async def on_call_tool(self, context, call_next): started = time.monotonic() outcome = "error:downstream_error" receipt = None - final_receipt_state = None - receipt_failure = None + invoked = False + phase = "admission" + fleet = None + + async def execute(): + nonlocal invoked, phase + invoked = True + phase = "dispatch" + return await call_next(context) + + def evidence(code=None, effect=None): + return for_call(name, arguments, phase=phase, + effect_state=effect or ("outcome_unknown" if invoked else "not_started"), + cause=code, fleet=fleet) + + async def finish(state, code, detail): + if receipt is not None: + await asyncio.to_thread(self.ledger.finish, receipt["receipt_id"], state, code, detail) + try: await asyncio.to_thread(self._check, arguments) if self.ledger is not None and self._receipt_worthy(name): - receipt = await asyncio.to_thread(self.ledger.start, name, self._fingerprint(name, arguments)) - lane_name = self._lane_name(name) - if lane_name: - result = await self.scheduler.run(name, arguments, lambda: call_next(context)) + pending = asyncio.create_task(asyncio.to_thread( + self.ledger.start, name, self._fingerprint(name, arguments), evidence())) + try: + receipt = await asyncio.shield(pending) + except asyncio.CancelledError: + # A cancelled to_thread wait can still write a starting + # receipt. Recover its ID before finalizing not_started. + receipt = await pending + raise + if self._lane_name(name): + result = await self.scheduler.run(name, arguments, execute) else: - result = await call_next(context) + result = await execute() + if name == "fleet_call" and isinstance(result.meta, dict): + candidate = result.meta.get("borg_fleet") + fleet = candidate if isinstance(candidate, dict) else None + phase = "output_check" dumped = result.model_dump() await asyncio.to_thread(self._check, dumped) if result.is_error: - code = classify_failure(dumped) - result.meta = {**(result.meta or {}), "borg_failure": failure_meta(code)} - final_receipt_state = "failed" if code in { - "authentication_required", "busy", "permission_denied", "policy_refused", - "admission_required", "capability_unavailable", "rate_limited" - } else "outcome_unknown" - if (result.meta or {}).get("borg_fleet", {}).get("state") == "not_started": - final_receipt_state = "failed" - receipt_failure = code + code = fleet.get("failure_code") if fleet else None + if not isinstance(code, str) or code not in FAILURE_MESSAGES: + code = classify_failure(dumped) + effect = "not_started" if fleet and fleet.get("state") == "not_started" else "outcome_unknown" + phase = "preflight" if effect == "not_started" else ( + "dispatch" if fleet and fleet.get("phase") == "dispatch" else "result") + detail = evidence(code, effect) + result.meta = {**(result.meta or {}), "borg_failure": {**failure_meta(code), **detail}} + await finish("failed" if effect == "not_started" else "outcome_unknown", code, detail) outcome = f"error:{code}" else: - final_receipt_state = "succeeded" + phase = "complete" + await finish("succeeded", None, evidence(effect="completed")) outcome = "ok" if receipt is not None: - await asyncio.to_thread(self.ledger.finish, receipt["receipt_id"], final_receipt_state, receipt_failure) result.meta = {**(result.meta or {}), "borg_operation_receipt": receipt["receipt_id"]} return result except asyncio.CancelledError: outcome = "error:request_cancelled" - if receipt is not None: - await asyncio.to_thread(self.ledger.finish, receipt["receipt_id"], "outcome_unknown", "request_cancelled") + detail = evidence("request_cancelled") + await finish("outcome_unknown" if invoked else "failed", "request_cancelled", detail) raise except ToolError as exc: - code = classify_failure(str(exc)) + from fleet_tools import FleetPreflightError + preflight = name == "fleet_call" and isinstance(exc, FleetPreflightError) + code = exc.code if preflight else classify_failure(exc) + if preflight: + phase = "preflight" + effect = "not_started" if not invoked or preflight else "outcome_unknown" outcome = f"error:{code}" - if receipt is not None: - state = "failed" if code in { - "authentication_required", "busy", "permission_denied", "policy_refused", - "admission_required", "capability_unavailable", "rate_limited" - } else "outcome_unknown" - await asyncio.to_thread(self.ledger.finish, receipt["receipt_id"], state, code) + await finish("failed" if effect == "not_started" else "outcome_unknown", code, evidence(code, effect)) if code == "credential_withheld" or isinstance(exc, ProcessOutputOffsetError): raise suffix = f" receipt={receipt['receipt_id']}" if receipt is not None else "" raise ToolError(f"BORG_{code.upper()}: {FAILURE_MESSAGES[code]}{suffix}") from None except Exception as exc: - code = classify_failure(str(exc)) + code = classify_failure(exc) outcome = f"error:{code}" - if receipt is not None: - await asyncio.to_thread(self.ledger.finish, receipt["receipt_id"], "outcome_unknown", code) + await finish("outcome_unknown" if invoked else "failed", code, evidence(code)) suffix = f" receipt={receipt['receipt_id']}" if receipt is not None else "" raise ToolError(f"BORG_{code.upper()}: {FAILURE_MESSAGES[code]}{suffix}") from None finally: diff --git a/connector/fleet_tools.py b/connector/fleet_tools.py index 9c762b2..fe0b846 100644 --- a/connector/fleet_tools.py +++ b/connector/fleet_tools.py @@ -27,6 +27,15 @@ "idempotentHint": True, "openWorldHint": True} +class FleetPreflightError(ToolError): + """A local refusal before dispatch, not an interpretation of target text.""" + def __init__(self, code: str): + if code not in {"bad_request", "identity_mismatch", "capability_unavailable"}: + raise ValueError("invalid fleet preflight code") + self.code = code + super().__init__(f"BORG_{code.upper()}: fleet preflight refused; no operation was started") + + def now() -> str: return datetime.now(timezone.utc).isoformat() @@ -121,7 +130,7 @@ def host(self, host: str) -> dict: rows = read_registry(self.registry)["hosts"] row = next((r for r in rows if r["id"] == host), None) if row is None or not row["enabled"]: - raise ToolError("Fleet host is unknown or disabled; no operation was started") + raise FleetPreflightError("capability_unavailable") return row @asynccontextmanager @@ -152,9 +161,9 @@ async def verify(client, row) -> dict: result = await client.call_tool("borg_identity", {}, timeout=15) identity = result.structured_content if not isinstance(identity, dict): - raise ToolError("Fleet target identity is unavailable; no operation was started") + raise FleetPreflightError("capability_unavailable") if any(identity.get(k) != row[k] for k in ("instance_id", "owner", "home")): - raise ToolError("Fleet target identity mismatch; no operation was started") + raise FleetPreflightError("identity_mismatch") return identity @staticmethod @@ -224,10 +233,16 @@ async def fleet_call(self, host: str, tool: str, arguments: dict, that machine. No automatic retry: after an unknown result inspect native state and target borg_operations_recent before repeating a change. """ - if (not re.fullmatch(r"[A-Za-z][A-Za-z0-9_]{0,127}", tool) or tool.startswith("fleet_") - or not 1000 <= timeout_ms <= 90000 - or len(json.dumps(arguments, allow_nan=False).encode()) > 262144): - raise ToolError("Invalid fleet call; no operation was started") + from computer_tools import classify_failure + try: + valid = (isinstance(tool, str) and re.fullmatch(r"[A-Za-z][A-Za-z0-9_]{0,127}", tool) + and not tool.startswith("fleet_") and type(timeout_ms) is int + and 1000 <= timeout_ms <= 90000 and isinstance(arguments, dict) + and len(json.dumps(arguments, allow_nan=False).encode()) <= 262144) + except (TypeError, ValueError): + valid = False + if not valid: + raise FleetPreflightError("bad_request") row = self.host(host) dispatched = False identity = None @@ -237,7 +252,7 @@ async def fleet_call(self, host: str, tool: str, arguments: dict, identity = await self.verify(client, row) schemas = await self.schemas(client) if tool not in {s["name"] for s in schemas}: - raise ToolError("Target tool is unavailable; no operation was started") + raise FleetPreflightError("capability_unavailable") dispatched = True async with asyncio.timeout(timeout_ms / 1000): result = await client.call_tool_mcp(tool, arguments, timeout=timeout_ms / 1000, @@ -248,16 +263,22 @@ async def fleet_call(self, host: str, tool: str, arguments: dict, return ToolResult(content=result.content, structured_content=result.structuredContent, is_error=result.isError, meta={**(result.meta or {}), "borg_fleet": { - "host": host, "identity": identity, "observed_at": now(), + "host": host, "tool": tool, "identity": identity, "observed_at": now(), + "state": "outcome_unknown" if result.isError else "succeeded", + "phase": "result", + "failure_code": classify_failure(result.model_dump()) if result.isError else None, "target_receipt": (result.meta or {}).get("borg_operation_receipt")}}) except asyncio.CancelledError: raise - except Exception: + except Exception as exc: state = "outcome_unknown" if dispatched else "not_started" + code = exc.code if isinstance(exc, FleetPreflightError) else classify_failure(exc) return ToolResult(content=f"Fleet call {state}. " + ( "The target may still be working. Inspect its native state and borg_operations_recent before retrying." if dispatched else "Verify the enrolled identity, SSH route and target tool before retrying."), - is_error=True, meta={"borg_fleet": {"host": host, "identity": identity, "state": state}}) + is_error=True, meta={"borg_fleet": { + "host": host, "tool": tool, "identity": identity, "state": state, + "phase": "dispatch" if dispatched else "preflight", "failure_code": code}}) def mount_fleet(server, fleet: Fleet): diff --git a/connector/job_tools.py b/connector/job_tools.py index 3ce0483..30545d3 100644 --- a/connector/job_tools.py +++ b/connector/job_tools.py @@ -31,6 +31,14 @@ "artifact_put", "artifact_read", "artifact_list"] +def _job_command(command: str) -> list[str]: + """Preserve the macOS login shell; support POSIX hosts without zsh.""" + for shell, option in (("/bin/zsh", "-lc"), ("/bin/sh", "-c")): + if os.path.isfile(shell) and os.access(shell, os.X_OK): + return [shell, option, command] + raise ToolError("BORG capability unavailable: no supported executable job shell") + + def _now() -> float: return time.time() @@ -166,6 +174,7 @@ def start(self, command: str, cwd: str | None = None, timeout_ms: int = 1000) -> working = _path(cwd) if cwd else Path.home() if not working.is_dir(): raise ToolError("BORG job working directory is unavailable") + shell_command = _job_command(command) job_id = str(uuid.uuid4()) job_dir = self.jobs / job_id job_dir.mkdir(mode=0o700) @@ -175,7 +184,7 @@ def start(self, command: str, cwd: str | None = None, timeout_ms: int = 1000) -> stdout = stdout_path.open("ab", buffering=0) stderr = stderr_path.open("ab", buffering=0) try: - proc = subprocess.Popen(["/bin/zsh", "-lc", command], cwd=str(working), + proc = subprocess.Popen(shell_command, cwd=str(working), stdin=subprocess.DEVNULL, stdout=stdout, stderr=stderr, start_new_session=True) except Exception: diff --git a/connector/operation_diagnostics.py b/connector/operation_diagnostics.py new file mode 100644 index 0000000..03269e5 --- /dev/null +++ b/connector/operation_diagnostics.py @@ -0,0 +1,74 @@ +"""Bounded operation metadata, never arguments, paths, payloads or error text. + +A failure class does not prove the absence of effects. Only execution phase or +local fleet preflight evidence may establish that a call was not dispatched. +""" +from __future__ import annotations + +import re +import uuid + +SCHEMA = "borg-operation-diagnostics/v1" +CAUSES = frozenset({ + "credential_withheld", "authentication_required", "busy", "permission_denied", + "policy_refused", "admission_required", "capability_unavailable", "rate_limited", + "timeout", "provider_unavailable", "resource_exhausted", "downstream_error", + "bad_request", "identity_mismatch", "request_cancelled", "process_interrupted", +}) +PHASES = frozenset({"admission", "preflight", "dispatch", "result", "output_check", "complete", "unknown"}) +EFFECTS = frozenset({"not_started", "outcome_unknown", "completed"}) +_TRANSIENT = frozenset({"busy", "rate_limited", "timeout", "provider_unavailable"}) +_HOST = re.compile(r"[a-z][a-z0-9_-]{0,47}\Z") +_TOOL = re.compile(r"[A-Za-z][A-Za-z0-9_]{0,127}\Z") + + +def _uuid(value): + if not isinstance(value, str): + return None + try: + return str(uuid.UUID(value)) + except (ValueError, AttributeError): + return None + + +def sanitize(value: dict | None) -> dict: + """Closed schema, applied again at the durable write boundary.""" + value = value if isinstance(value, dict) else {} + phase = value.get("phase") + effect = value.get("effect_state") + cause = value.get("cause") + phase = phase if isinstance(phase, str) and phase in PHASES else "unknown" + effect = effect if isinstance(effect, str) and effect in EFFECTS else "outcome_unknown" + cause = cause if isinstance(cause, str) and cause in CAUSES else None + out = {"schema": SCHEMA, "phase": phase, "effect_state": effect, "cause": cause, + "retryable": effect == "not_started" and cause in _TRANSIENT, + "retry_without_change": False} + target = value.get("target") + if isinstance(target, dict): + safe = {} + for key, pattern in (("host", _HOST), ("tool", _TOOL)): + item = target.get(key) + if isinstance(item, str) and pattern.fullmatch(item): + safe[key] = item + for key in ("instance_id", "server_generation", "receipt_id"): + item = _uuid(target.get(key)) + if item is not None: + safe[key] = item + if safe: + out["target"] = safe + return out + + +def for_call(name: str, arguments: dict, *, phase: str, effect_state: str, + cause: str | None = None, fleet: dict | None = None) -> dict: + target = {} + if name == "fleet_call": + arguments = arguments if isinstance(arguments, dict) else {} + target = {"host": arguments.get("host"), "tool": arguments.get("tool")} + # Only the local Fleet wrapper establishes identity before dispatch. + if isinstance(fleet, dict): + identity = fleet.get("identity") + if isinstance(identity, dict): + target.update({k: identity.get(k) for k in ("instance_id", "server_generation")}) + target["receipt_id"] = fleet.get("target_receipt") + return sanitize({"phase": phase, "effect_state": effect_state, "cause": cause, "target": target}) diff --git a/connector/operation_receipts.py b/connector/operation_receipts.py index 4852952..517fa26 100644 --- a/connector/operation_receipts.py +++ b/connector/operation_receipts.py @@ -16,6 +16,7 @@ from typing import Any from fastmcp.exceptions import ToolError +from operation_diagnostics import sanitize FINAL = frozenset({"succeeded", "failed", "outcome_unknown"}) @@ -83,10 +84,11 @@ def _write(self, row: dict[str, Any]) -> None: except FileNotFoundError: pass - def start(self, tool: str, request_sha256: str) -> dict[str, Any]: + def start(self, tool: str, request_sha256: str, diagnostics: dict | None = None) -> dict[str, Any]: receipt_id = str(uuid.uuid4()) row = { - "version": 1, + "version": 2, + "diagnostics": sanitize(diagnostics), "receipt_id": receipt_id, "tool": tool, "request_sha256": request_sha256, @@ -101,7 +103,8 @@ def start(self, tool: str, request_sha256: str) -> dict[str, Any]: self._write(row) return row - def finish(self, receipt_id: str, state: str, failure_code: str | None = None) -> dict[str, Any]: + def finish(self, receipt_id: str, state: str, failure_code: str | None = None, + diagnostics: dict | None = None) -> dict[str, Any]: if state not in FINAL: raise ValueError("invalid final operation state") receipt_id = _uuid(receipt_id) @@ -112,6 +115,16 @@ def finish(self, receipt_id: str, state: str, failure_code: str | None = None) - row["state"] = state row["finished_at"] = _now() row["failure_code"] = failure_code + detail = sanitize(diagnostics if diagnostics is not None else row.get("diagnostics")) + detail["cause"] = failure_code + if state == "outcome_unknown": + detail["effect_state"] = "outcome_unknown" + if diagnostics is None: + detail["phase"] = "unknown" + elif state == "succeeded": + detail["effect_state"] = "completed" + detail["phase"] = "complete" + row["diagnostics"] = sanitize(detail) self._write(row) return row diff --git a/connector/recall_policy.py b/connector/recall_policy.py new file mode 100644 index 0000000..e6e1383 --- /dev/null +++ b/connector/recall_policy.py @@ -0,0 +1,111 @@ +"""Conservative, read-only selection at both public memory boundaries. + +Similarity is not confidence. Semantic searches remain candidate discovery; only +explicit quoted phrases and opaque identifiers require literal evidence. This is +not entity resolution, a corpus-wide absence proof, or an authority promotion. +""" +from __future__ import annotations + +import math +import re +from typing import Any + +from fastmcp.exceptions import ToolError + +SCHEMA = "borg-recall-selection/v1" +# Whole-record rules only: discussion or diagnosis of these strings is retained. +_VACUOUS = frozenset({ + "the tool used is tool.", "the tool used is tool", + "the tool is tool.", "the tool is tool", + "the tool is called 'tool'.", 'the tool is called "tool".', +}) +_QUOTED = re.compile(r'"([^"\n]+)"') +_TOKEN = re.compile(r"[\w][\w.:-]*", re.UNICODE) +_HEX = re.compile(r"[0-9a-f]{16,}\Z", re.IGNORECASE) +_HEX_PART = re.compile(r"(?:^|[_.:-])[0-9a-f]{8,}(?:$|[_.:-])", re.IGNORECASE) +_UUID = re.compile(r"[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\Z", re.IGNORECASE) + + +def _normalized(text: str) -> str: + return " ".join(text.split()).casefold() + + +def _opaque(token: str) -> bool: + return bool(_UUID.fullmatch(token) or _HEX.fullmatch(token) + or (len(token) >= 16 and any(c.isdigit() for c in token) and _HEX_PART.search(token))) + + +def exact_terms(query: str) -> list[str]: + """Do not guess identities from ordinary capitalized names or short words.""" + terms = [_normalized(value) for value in _QUOTED.findall(query) if value.strip()] + for match in _TOKEN.finditer(query): + token = match.group().rstrip(".:") + if _opaque(token): + terms.append(_normalized(token)) + return list(dict.fromkeys(terms)) + + +def _contains(text: str, term: str) -> bool: + # Literal term with token boundaries: an ID prefix is not that ID, and a + # surname prefix is not a full surname. No fuzzy identity assertion. + if _opaque(term): + # Token equality rejects ID suffixes while allowing sentence punctuation. + return any(match.group().rstrip(".:") == term for match in _TOKEN.finditer(text)) + return re.search(r"(? tuple[list[dict], dict]: + if not isinstance(rows, list): + raise ToolError("BORG upstream response has an unexpected shape") + requested = max(1, min(int(limit), 12)) + terms = exact_terms(query) + omitted = {"malformed": 0, "vacuous": 0, "invalid_score": 0, "exact_mismatch": 0} + eligible = [] + for row in rows: + if not isinstance(row, dict) or not isinstance(row.get("memory"), str) or not row["memory"].strip(): + omitted["malformed"] += 1 + continue + text = _normalized(row["memory"]) + if text in _VACUOUS: + omitted["vacuous"] += 1 + continue + score = row.get("score") + # Missing scores are legacy-compatible; no uncalibrated numeric cutoff. + try: + valid_score = score is None or (not isinstance(score, bool) + and isinstance(score, (int, float)) and math.isfinite(score)) + except OverflowError: + valid_score = False + if not valid_score: + omitted["invalid_score"] += 1 + continue + native_id = _normalized(row["id"]) if isinstance(row.get("id"), str) else None + if terms and not all(_contains(text, term) or (_opaque(term) and term == native_id) + for term in terms): + omitted["exact_mismatch"] += 1 + continue + eligible.append(row) + selected = eligible[:requested] + details = { + "schema": SCHEMA, + "status": "candidates" if selected else "sparse", + "match_policy": "literal_terms" if terms else "semantic_candidates", + "exact_term_count": len(terms), + "candidate_count": len(rows), + "eligible_count": len(eligible), + "returned_count": len(selected), + "omitted": omitted, + "limit_omitted": max(0, len(eligible) - len(selected)), + "coverage": "upstream_candidate_pool_only", + "notice": "Similarity is not confidence. Sparse results do not prove absence from the corpus; no authority is inferred.", + } + return selected, {"result_floor": "sparse" if len(selected) < requested else "populated", + "retrieval": details} + + +def unavailable_retrieval() -> dict: + """A failed upstream is never disguised as a successful empty search.""" + return {"result_floor": "unavailable", "retrieval": { + "schema": SCHEMA, "status": "unavailable", "candidate_count": None, + "returned_count": 0, "coverage": "unavailable", + }} diff --git a/connector/requirements-test.in b/connector/requirements-test.in new file mode 100644 index 0000000..06ffe19 --- /dev/null +++ b/connector/requirements-test.in @@ -0,0 +1,4 @@ +# Connector-only CI dependencies, constrained by the accepted runtime lock. +fastmcp==3.4.7 +httpx==0.28.1 +starlette==1.6.0 diff --git a/connector/requirements-test.lock b/connector/requirements-test.lock new file mode 100644 index 0000000..457f578 --- /dev/null +++ b/connector/requirements-test.lock @@ -0,0 +1,1170 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile --generate-hashes --python-version 3.12 --universal --constraint installer/requirements.lock connector/requirements-test.in --output-file connector/requirements-test.lock +aiofile==3.12.3 \ + --hash=sha256:5c1bcc9e929c50834608e8cc1a4cc1d7503eb60c15a535b779fd39e2f372c017 \ + --hash=sha256:caa6aa746b5e47e2165f7abd741b6415e49cf4d44fddc0f61844612cc3924d41 + # via + # -c installer/requirements.lock + # py-key-value-aio +annotated-types==0.8.0 \ + --hash=sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7 \ + --hash=sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0 + # via + # -c installer/requirements.lock + # pydantic +anyio==4.15.1 \ + --hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101 \ + --hash=sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94 + # via + # -c installer/requirements.lock + # httpx + # mcp + # py-key-value-aio + # sse-starlette + # starlette + # watchfiles +attrs==26.1.0 \ + --hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 \ + --hash=sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32 + # via + # -c installer/requirements.lock + # cyclopts + # jsonschema + # jsonschema-path + # referencing +authlib==1.8.0 \ + --hash=sha256:88aebbd9af6757e14e912d5dc007ae1dc1f3e27e3b2152ce7c552ee2c3b3c121 \ + --hash=sha256:f3ecd5f1da737262fb53bf1a4d95c4ea1ad9dd509316587a255c99ab1838a4f0 + # via + # -c installer/requirements.lock + # fastmcp-slim +beartype==0.22.9 \ + --hash=sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f \ + --hash=sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2 + # via + # -c installer/requirements.lock + # py-key-value-aio +cachetools==7.1.8 \ + --hash=sha256:1221d547a0b24b7f26fa891d40d488b5258beab9aebd8ed68c729be3af849c43 \ + --hash=sha256:a81e3844acaa7355b6567f97bd67a94a14ec3a9bc2cbbdae45b9592cc036775b + # via + # -c installer/requirements.lock + # py-key-value-aio +caio==0.12.4 \ + --hash=sha256:0d8d2826bf622644fc374a9f53ac00918b489d03599783953fcbe79af19123f5 \ + --hash=sha256:134d9d145d75f454de5ece9e87595bad433639b51061b693bafc369f689f8742 \ + --hash=sha256:17a41de51203fc4d787d8d577bd56f169167aa9822f14fada015504688791aa4 \ + --hash=sha256:1c1a387f8784a86a56a8912ca42e1ad67599f16406f497b5b655b461a1b89426 \ + --hash=sha256:1e1d5570fd0ec75b1b2c2877c5545ed9f0738b5d23b000e2b6a8fc830dc8b310 \ + --hash=sha256:20acd9a8df90d25b63bc0b7dc264e2a09b99082a420b5d640c76056027ee6b52 \ + --hash=sha256:20f1d3fe05be285d82413b00bd8061eeb62d3027d3f565adaf7db10bebc594dc \ + --hash=sha256:27ec5671ac05650abc7ac1fb12e95ad09417ae495ce9be565927786688edd6c5 \ + --hash=sha256:32d8e9f3e2099c8db29446679252766c9bcd806eb88b4fb60ad274f73df2a5e9 \ + --hash=sha256:413565d77dfdf2dd841ac100571ef1cc710f9c56137312f3ec51356350b4f3a5 \ + --hash=sha256:4189104e5579553031340a677762398c130b81ddb7d7c9b69eebffddaecc4f58 \ + --hash=sha256:456a93868ff007cea65d916d96d192cda8b9992c6eee046cb0159040f9446ab5 \ + --hash=sha256:4854a029e359e5ddfb5589ab66157a34af6a0dda0acdaa43f5e640d60182c1ea \ + --hash=sha256:4f2e0b6d393dc0ea78cfd13a9a4321fc35fd6a2db802e5f64651317e859fc929 \ + --hash=sha256:51b8600eadc8756751dfc152514852040c6c28deb6bdcd6bfbdddb2a38e424e1 \ + --hash=sha256:53605636e3b1eaeca368b475f1471ad9774736c6502a4b58b8b6b8d74b531770 \ + --hash=sha256:67b725641fea682a2e9b1d3b2a150d21f1a25383e3ec8351bd7677ff857fa982 \ + --hash=sha256:67f1bddc997bef281db36600e3e58d6b16d6486b3540eb62c9e54c6584f137fd \ + --hash=sha256:710ffe4a6f3d69dbfe98e9f856c24a544e4d639c7bd210f9f5503a26e62502f4 \ + --hash=sha256:759b952df48a5bbdb16e90631c748c877fb0ec7ca9d64eef1066569925ccdd30 \ + --hash=sha256:780465251a0680039b29c990bca73b40b843e39d06505c62e353ed56490e4576 \ + --hash=sha256:7a5e231bbf81eaed269f99afa9ef46457f51f9407952a1795c0795b3a62c23c0 \ + --hash=sha256:8a926d562f8c06767774a91239770ebaa93c10a24074e21ea741db208df9cc1d \ + --hash=sha256:8e9eda6a88f309a0a53bcba844471638da01a92758015e2df099fa361891dc72 \ + --hash=sha256:97bff8f7da696321552b42629d1869983a8ced2f010bac9731b2719c1195a20c \ + --hash=sha256:9dc0fd6f09ef72d18d3f43ca3d1140295222d925c583114ab9b1e8843a109a6e \ + --hash=sha256:b0444d20067642bb272e423de3cc8ef05bf134d335d57fa5bddcfcc98e04313e \ + --hash=sha256:cd1d00ed1867a3b7a4cffb64b1bd8644de4b6f88cb25240cf82a9cc160ace975 \ + --hash=sha256:ceee64265a55b9aba9c38c1d01a159b99f1e36eb56938022018199d2e7fe1742 \ + --hash=sha256:ddf8d946b795ebd7c75b331b6dcbab4808fdd2c9f16ca76012b4757512861133 \ + --hash=sha256:e22ce2e69d94e4c80e0c11c871e547b3c2d147f977632894cb2527ddb6e87a8d \ + --hash=sha256:ee9de68d6ede7ed69e55a1c09073ce146d9f9470088796d55238c485a93fe203 \ + --hash=sha256:f063624a98a64bab387430c5eaea61ef3825a98399104a297b8da4741c15139f + # via + # -c installer/requirements.lock + # aiofile +certifi==2026.7.22 \ + --hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 \ + --hash=sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55 + # via + # -c installer/requirements.lock + # httpcore + # httpx +cffi==2.1.1 ; platform_python_implementation != 'PyPy' \ + --hash=sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e \ + --hash=sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66 \ + --hash=sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2 \ + --hash=sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0 \ + --hash=sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6 \ + --hash=sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971 \ + --hash=sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c \ + --hash=sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d \ + --hash=sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9 \ + --hash=sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517 \ + --hash=sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735 \ + --hash=sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80 \ + --hash=sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f \ + --hash=sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1 \ + --hash=sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29 \ + --hash=sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8 \ + --hash=sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c \ + --hash=sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e \ + --hash=sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48 \ + --hash=sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813 \ + --hash=sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac \ + --hash=sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632 \ + --hash=sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6 \ + --hash=sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1 \ + --hash=sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659 \ + --hash=sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688 \ + --hash=sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004 \ + --hash=sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0 \ + --hash=sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062 \ + --hash=sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779 \ + --hash=sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94 \ + --hash=sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50 \ + --hash=sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab \ + --hash=sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac \ + --hash=sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6 \ + --hash=sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676 \ + --hash=sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1 \ + --hash=sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9 \ + --hash=sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf \ + --hash=sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13 \ + --hash=sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e \ + --hash=sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e \ + --hash=sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973 \ + --hash=sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527 \ + --hash=sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72 \ + --hash=sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890 \ + --hash=sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c \ + --hash=sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990 \ + --hash=sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd \ + --hash=sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9 \ + --hash=sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94 \ + --hash=sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3 \ + --hash=sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80 \ + --hash=sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41 \ + --hash=sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5 \ + --hash=sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c \ + --hash=sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a \ + --hash=sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4 \ + --hash=sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e \ + --hash=sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6 \ + --hash=sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98 \ + --hash=sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b \ + --hash=sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1 \ + --hash=sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03 \ + --hash=sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af \ + --hash=sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231 \ + --hash=sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2 \ + --hash=sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3 \ + --hash=sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836 \ + --hash=sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5 \ + --hash=sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399 \ + --hash=sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96 \ + --hash=sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e \ + --hash=sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be \ + --hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf \ + --hash=sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc \ + --hash=sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455 \ + --hash=sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0 \ + --hash=sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12 \ + --hash=sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b \ + --hash=sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7 \ + --hash=sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692 \ + --hash=sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54 \ + --hash=sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3 \ + --hash=sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b \ + --hash=sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be \ + --hash=sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d \ + --hash=sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358 \ + --hash=sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a \ + --hash=sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7 \ + --hash=sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc \ + --hash=sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960 \ + --hash=sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125 \ + --hash=sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb \ + --hash=sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a \ + --hash=sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa \ + --hash=sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf \ + --hash=sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3 \ + --hash=sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4 \ + --hash=sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264 + # via + # -c installer/requirements.lock + # cryptography +click==8.5.0 \ + --hash=sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360 \ + --hash=sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34 + # via + # -c installer/requirements.lock + # uvicorn +cryptography==50.0.1 \ + --hash=sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71 \ + --hash=sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23 \ + --hash=sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6 \ + --hash=sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e \ + --hash=sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361 \ + --hash=sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054 \ + --hash=sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f \ + --hash=sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6 \ + --hash=sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49 \ + --hash=sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5 \ + --hash=sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149 \ + --hash=sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88 \ + --hash=sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad \ + --hash=sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a \ + --hash=sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f \ + --hash=sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2 \ + --hash=sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20 \ + --hash=sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45 \ + --hash=sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f \ + --hash=sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b \ + --hash=sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527 \ + --hash=sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3 \ + --hash=sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6 \ + --hash=sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367 \ + --hash=sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0 \ + --hash=sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94 \ + --hash=sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239 \ + --hash=sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b \ + --hash=sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a \ + --hash=sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9 \ + --hash=sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5 \ + --hash=sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc \ + --hash=sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648 \ + --hash=sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986 \ + --hash=sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959 \ + --hash=sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0 \ + --hash=sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17 \ + --hash=sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e \ + --hash=sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733 \ + --hash=sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f \ + --hash=sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8 \ + --hash=sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf \ + --hash=sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671 \ + --hash=sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80 \ + --hash=sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558 \ + --hash=sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef + # via + # -c installer/requirements.lock + # authlib + # joserfc + # pyjwt + # secretstorage +cyclopts==4.25.2 \ + --hash=sha256:0776bc1fa796cd351646c345b7420279e58d6c2c4a8f5d5dd54dea85bdb2de8f \ + --hash=sha256:51b42513eea5e4ba6a08b68acf187b81f1191032cf77b9631086cfa2c88fa7a4 + # via + # -c installer/requirements.lock + # fastmcp-slim +dnspython==2.8.0 \ + --hash=sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af \ + --hash=sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f + # via + # -c installer/requirements.lock + # email-validator +docstring-parser==0.18.0 \ + --hash=sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015 \ + --hash=sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b + # via + # -c installer/requirements.lock + # cyclopts +email-validator==2.3.0 \ + --hash=sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4 \ + --hash=sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426 + # via + # -c installer/requirements.lock + # pydantic +exceptiongroup==1.3.1 \ + --hash=sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219 \ + --hash=sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598 + # via + # -c installer/requirements.lock + # fastmcp-slim +fastmcp==3.4.7 \ + --hash=sha256:43117aca886f5ee2f6a569bba91cef02b59c339aad04ba29950ff18d251c822a \ + --hash=sha256:e4e7698cb4af5bc667b1901685261fa2f3526dc73d243a461fca42500c8dbe56 + # via + # -c installer/requirements.lock + # -r connector/requirements-test.in +fastmcp-slim==3.4.7 \ + --hash=sha256:06b32a358320a7dc2b2ee040ba89ea55ddc20763dff2949f384f7974b13b5d8f \ + --hash=sha256:6c931a0089705f3f2935428ef9b2bc74ad94140adc64aab84d116d103e694b3a + # via + # -c installer/requirements.lock + # fastmcp +griffelib==2.3.0 \ + --hash=sha256:1b8f9cd525681c26b1d6d574faa1371651e8459ca51d209684f50b8096ae06e0 \ + --hash=sha256:7b0952caf5bca6afa4bb5ee8c6a2d183fe3f21b62efc5f6c7243cb2b26d2d115 + # via + # -c installer/requirements.lock + # fastmcp-slim +h11==0.16.0 \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + # via + # -c installer/requirements.lock + # httpcore + # uvicorn +httpcore==1.0.9 \ + --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \ + --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8 + # via + # -c installer/requirements.lock + # httpx +httpx==0.28.1 \ + --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ + --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad + # via + # -c installer/requirements.lock + # -r connector/requirements-test.in + # fastmcp-slim + # mcp +httpx-sse==0.4.3 \ + --hash=sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc \ + --hash=sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d + # via + # -c installer/requirements.lock + # mcp +idna==3.19 \ + --hash=sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15 \ + --hash=sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4 + # via + # -c installer/requirements.lock + # anyio + # email-validator + # httpx +jaraco-classes==3.4.0 \ + --hash=sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd \ + --hash=sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 + # via + # -c installer/requirements.lock + # keyring +jaraco-context==6.1.2 \ + --hash=sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535 \ + --hash=sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3 + # via + # -c installer/requirements.lock + # keyring +jaraco-functools==4.6.0 \ + --hash=sha256:880c577ec9720b3a052d5bc611fb9f2269b3d87902ef42440df443b88e443280 \ + --hash=sha256:99e3dc0060c5cbe8fcd1cdb36258e2a65ca40f1566b2033b12abb1bb44dd3c30 + # via + # -c installer/requirements.lock + # keyring +jeepney==0.9.0 ; sys_platform == 'linux' \ + --hash=sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683 \ + --hash=sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732 + # via + # keyring + # secretstorage +joserfc==1.7.5 \ + --hash=sha256:add2c2c84e8373b084d526a8b53daba5d7a513a118cd2dcd9fc9f979d0922159 \ + --hash=sha256:d5ff536e658e17664f8c1b1ab60dc4aa62aa973fcef1edd33cc44bda45d6f5ea + # via + # -c installer/requirements.lock + # authlib + # fastmcp-slim +jsonref==1.1.0 \ + --hash=sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552 \ + --hash=sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9 + # via + # -c installer/requirements.lock + # fastmcp-slim +jsonschema==4.26.0 \ + --hash=sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326 \ + --hash=sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce + # via + # -c installer/requirements.lock + # mcp +jsonschema-path==0.5.0 \ + --hash=sha256:2790a070bc7abb08ea3dbe4d340ece4efadf639223001f020c7503229ba068e2 \ + --hash=sha256:493b156ba895c97602655b620a8456caa2ce08c1aa389f5a7addec065e6e855c + # via + # -c installer/requirements.lock + # fastmcp-slim +jsonschema-specifications==2025.9.1 \ + --hash=sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe \ + --hash=sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d + # via + # -c installer/requirements.lock + # jsonschema +keyring==25.7.0 \ + --hash=sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f \ + --hash=sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b + # via + # -c installer/requirements.lock + # py-key-value-aio +markdown-it-py==4.2.0 \ + --hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \ + --hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a + # via + # -c installer/requirements.lock + # rich +mcp==1.30.0 \ + --hash=sha256:445414625fce5c295faa505bb11bacece661ab6f4028d57c935db57820b7a3e4 \ + --hash=sha256:666edb5009503e1047c9d60346a756f94b261f05cc2625f23d41c728ffc484d0 + # via + # -c installer/requirements.lock + # fastmcp-slim +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via + # -c installer/requirements.lock + # markdown-it-py +more-itertools==11.1.0 \ + --hash=sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d \ + --hash=sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192 + # via + # -c installer/requirements.lock + # jaraco-classes + # jaraco-functools +openapi-pydantic==0.5.1 \ + --hash=sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146 \ + --hash=sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d + # via + # -c installer/requirements.lock + # fastmcp-slim +opentelemetry-api==1.44.0 \ + --hash=sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a \ + --hash=sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef + # via + # -c installer/requirements.lock + # fastmcp-slim +packaging==26.3 \ + --hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \ + --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c + # via + # -c installer/requirements.lock + # fastmcp-slim +pathable==0.6.0 \ + --hash=sha256:6404b8b82aef5ff0fd478934137128b99b12212ba35afdde5525ca4f8388ea58 \ + --hash=sha256:82c4ca6c98c502ad12e0d4e9779b6210afee93c38990988c8c5d1b49bdcdf566 + # via + # -c installer/requirements.lock + # jsonschema-path +platformdirs==4.11.8 \ + --hash=sha256:52f2f181bbfde907966932cc8312d967d02976422d66d537ea16092b8e291081 \ + --hash=sha256:f23abafea7dd4276d1f29104b83598d7dcc567cafd07c9c951e66665645437fc + # via + # -c installer/requirements.lock + # fastmcp-slim +py-key-value-aio==0.4.5 \ + --hash=sha256:ab862adbcb8c72547d1c57821f22cbbb71ab86509039c96f36e914e0336c8dd7 \ + --hash=sha256:c6563a2c6abe5da5e20f4f9e875c2a9b425a2244a54fadbf46cf140a9eea45d7 + # via + # -c installer/requirements.lock + # fastmcp-slim +pycparser==3.0 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' \ + --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ + --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 + # via + # -c installer/requirements.lock + # cffi +pydantic==2.13.5 \ + --hash=sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73 \ + --hash=sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08 + # via + # -c installer/requirements.lock + # fastmcp-slim + # mcp + # openapi-pydantic + # pydantic-settings +pydantic-core==2.46.5 \ + --hash=sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942 \ + --hash=sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821 \ + --hash=sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5 \ + --hash=sha256:0bddb4020d8f04175865ccd17eff3040874fc11fb593f424edb452653b4b947c \ + --hash=sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184 \ + --hash=sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2 \ + --hash=sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc \ + --hash=sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5 \ + --hash=sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0 \ + --hash=sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931 \ + --hash=sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e \ + --hash=sha256:1e449def1945a462c464331254e5a44fca7c3b4f9aedf59ec2f50f8066dd8e25 \ + --hash=sha256:1e5aad1220a1192c42341c8fd4a8686657e73ab2a920c970bdc4de334fe3193d \ + --hash=sha256:200aa3dc9f8d54f0754f43247c0bad0999fdcfbfd2488384dd44f37279271fe6 \ + --hash=sha256:2471fd51c61c610e1dcf7de44d7299283661654d11264ab4802b303368d69c47 \ + --hash=sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038 \ + --hash=sha256:28a6a556cd3b6066bea827857f9d9cce027c96f776e512f544a581f9e42161f8 \ + --hash=sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b \ + --hash=sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a \ + --hash=sha256:2d330aaba8621b1edcec8ae2c4050f63b84ccf6d98723a8f212e9684713abf0e \ + --hash=sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074 \ + --hash=sha256:337639ba62a11acde6ef3aeb08c8ea755f8ef1fe5e513356c0f36a2b0d7568b0 \ + --hash=sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433 \ + --hash=sha256:356c8368cbc321050b169595683a2e1d63413b1e0e2868b330af9fc14c616d3f \ + --hash=sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034 \ + --hash=sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21 \ + --hash=sha256:3a3e26b6a8274211bddee2d0e4d0d42778f17a34510f49d2ec44b58abfc41736 \ + --hash=sha256:3aa166e99c4f2985407fb8714aebede877ecb5455cf321b606adca926d30d5a0 \ + --hash=sha256:3d2652072b2d774947ba5cf78a9e59644ac62ee572daf6dd2e1dfe905e15b2b7 \ + --hash=sha256:40375c2d05acec10323e45dfe2077ac44bc74659008614af5069034e2cfc781c \ + --hash=sha256:413a717a410d0c817ef5b786a059415550b3794e1d0c2abffd9efb93a3d9f7b4 \ + --hash=sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c \ + --hash=sha256:49776eab08766a08dfff7012f8b422dcd7e25e43b316eedf0477c24fcfa84b7c \ + --hash=sha256:4d44cf99ddebf875f9b68cc267aa684c99b7b44fe63ee1cac4ec163807290069 \ + --hash=sha256:4dedce55295becb61921e386b99d4f2706045306e7fa52249a33004c837379fb \ + --hash=sha256:4f8507560a9284e1370bb048ed4282012fbef4e8d109875b95e884d228552061 \ + --hash=sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29 \ + --hash=sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3 \ + --hash=sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa \ + --hash=sha256:53feb344243bb9510a9dec7bf3cf1b64d88a98af5dc7872a5160465f8b198c8e \ + --hash=sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38 \ + --hash=sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f \ + --hash=sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b \ + --hash=sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8 \ + --hash=sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e \ + --hash=sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c \ + --hash=sha256:5f93c5fe914d75fbec9a49209b00da5f08e9e467d69da2b1510c81940cfd10be \ + --hash=sha256:657b40d6240c0a7b6a64b30f22d1e3aa631c7e846c621b0c0f6d1d75e2e15ea6 \ + --hash=sha256:6d30e1a4f138b8951063e9a394752a9179b51da288ffa507b1e659222f4c1793 \ + --hash=sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1 \ + --hash=sha256:701b2e04b560eeb4bddf7a25ab8ca476176e34fdbd9a0e18196f0d12d4685f0b \ + --hash=sha256:771cf63ae0b1b50dd22e5f3e3549fab5f3f4ff1635d352a9e1a97fe01c7b2e64 \ + --hash=sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f \ + --hash=sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761 \ + --hash=sha256:7b0fc826b16c55e561e5d2a0c5c77b051ba1d92808118c4e4b5390f5e0cf191d \ + --hash=sha256:7c6be839a5a8312626b32029a415644a0846b420bc8b52b95b28cd92da162168 \ + --hash=sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869 \ + --hash=sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111 \ + --hash=sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5 \ + --hash=sha256:850a08d167dde16db8702c274f320c7be9d7da6f6dff2b58b18f9e815bd94f5b \ + --hash=sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7 \ + --hash=sha256:892a881d5f68c2b9ea304b7a6c2c60d9343df578a311b0f86b94bc8f1ffe8129 \ + --hash=sha256:895395f8918627b04efb1ad2a4cf605387143300ba03304cd1dfa6d03f5e095e \ + --hash=sha256:8b10e3e8fd7ddc2bd915848a2768e44c15b22936f1cc54c462ad1164deb02655 \ + --hash=sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c \ + --hash=sha256:8feeac04b5794e513e710af2f9c87d49f31a6dc47967bb264a1fed61a8989bec \ + --hash=sha256:9432f3598db432cb51c5b37fdbf29a60fcccc79e30d37a05022776a6bc4ab689 \ + --hash=sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f \ + --hash=sha256:978e7b97d4824b5be09c69fb70507cbde3b0323fc147332ca40a94d9a6a0ebbf \ + --hash=sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea \ + --hash=sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9 \ + --hash=sha256:9b68938dd5b0c783d88ff8e2dcc69451b5eb936fe212d516b21b9d5567f6d464 \ + --hash=sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519 \ + --hash=sha256:9f47b8a949e60f027f0aa0a6f6c7b7e9c55cbf4380d10b344e282fa4e7ab1e1b \ + --hash=sha256:a1dee1b804ff4d11c663636cf15d2ea47e9f79cd56c033fb1cbf08924842a48f \ + --hash=sha256:a2468d93d181667a7abd66e1b64bb9f76f361b0fef8faddf687456453576f5ee \ + --hash=sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a \ + --hash=sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e \ + --hash=sha256:a445486499897b88a7d6c310c88ed64dd37b1b59bfd7ae9107490bbb362f47d6 \ + --hash=sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2 \ + --hash=sha256:ab4b66edffb32d9e951efb3814bd104b8367a7501b81b955cacb5726d897389f \ + --hash=sha256:aca6c767f552b21b10f774aeac128e828eafb796adfa1b666a18bf6321453c3a \ + --hash=sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f \ + --hash=sha256:b10ec717381bdbfafef34607824db4c91de69ff085e4fca3b2af91b4fa17e68a \ + --hash=sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47 \ + --hash=sha256:b6acfb46a814762367fb7ba0828b0a17d441b92ce249a0e007474c9072662dda \ + --hash=sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0 \ + --hash=sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4 \ + --hash=sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d \ + --hash=sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3 \ + --hash=sha256:c14ad3bdc85ee7f318742c457ca3968a92126d144b15721c759033bfb06296c2 \ + --hash=sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355 \ + --hash=sha256:c3471e5c4a949c26ec00a77f01df59096aa9495877de76fd60a980f8ee6be461 \ + --hash=sha256:c583b927a8838dab890706a6fa7573fbb8b70e24000ef9f7238e2d6f6435a5ed \ + --hash=sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f \ + --hash=sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5 \ + --hash=sha256:cd5214352ae68f3b5e9af7768bdc5253695ee069675db3480518420b3be881f2 \ + --hash=sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829 \ + --hash=sha256:cdc8b74ecc48c0cb1e9607a05ec4e9e88db60a19ffcc9a1d5f9088ede40c8dc0 \ + --hash=sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266 \ + --hash=sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575 \ + --hash=sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290 \ + --hash=sha256:d625a186a65201c23a9e3b8ed9c47e90a026e03256608cc91851c6709096844f \ + --hash=sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62 \ + --hash=sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f \ + --hash=sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a \ + --hash=sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7 \ + --hash=sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed \ + --hash=sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f \ + --hash=sha256:eb7d8d0e5886a89a55d2eef490e272fa965a9d57c6b29a5b5088a7997ec2cad1 \ + --hash=sha256:ecb42011e12ee19cafbc312887cbf3546959fe02fbad44f272d4be5baa997615 \ + --hash=sha256:ef3fbbf161dc9351a2fe0422e51b129f9e97e42385bd0320b309c15f7d287dd8 \ + --hash=sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3 \ + --hash=sha256:f077d0b97ab11fa7dcc633fca53515f290bca8a8a633e966d5b6d1879d9ed01a \ + --hash=sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff \ + --hash=sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084 \ + --hash=sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0 \ + --hash=sha256:fc5d783bd4a2387e97b8a2d5ec781cfb92b3d893bf82370548e99db5915935d3 \ + --hash=sha256:fc8515076c11f3cfdf4fb142dcca0fe384b1230a3b5415458ac84f3e0903ec13 \ + --hash=sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9 + # via + # -c installer/requirements.lock + # pydantic +pydantic-settings==2.15.0 \ + --hash=sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42 \ + --hash=sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117 + # via + # -c installer/requirements.lock + # fastmcp-slim + # mcp +pygments==2.21.0 \ + --hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \ + --hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c + # via + # -c installer/requirements.lock + # rich + # rich-rst +pyjwt==2.14.0 \ + --hash=sha256:77283c83fb56ecf566a886c757a714bc83668e38156de2cce8263302f42e0b86 \ + --hash=sha256:ad0cef71c756a56e74863c2919cf0985f72decbcfcb550ee2f422e7c62b5eedc + # via + # -c installer/requirements.lock + # mcp +pyperclip==1.11.0 \ + --hash=sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6 \ + --hash=sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273 + # via + # -c installer/requirements.lock + # fastmcp-slim +python-dotenv==1.2.3 \ + --hash=sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9 \ + --hash=sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35 + # via + # -c installer/requirements.lock + # fastmcp-slim + # pydantic-settings +python-multipart==0.0.32 \ + --hash=sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e \ + --hash=sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23 + # via + # -c installer/requirements.lock + # fastmcp-slim + # mcp +pywin32==312 ; sys_platform == 'win32' \ + --hash=sha256:02ebca0f0242b75292e218065004310d6a477407c09fa449bfe4f6022bc0c0fc \ + --hash=sha256:17948aeadbdb091f0ced6ef0841620794e68327b94ee415571c1203594b7215c \ + --hash=sha256:3020656e34f1cf7faeb7bccd2b84653a607c6ff0c55ada85e6487d61716deabd \ + --hash=sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831 \ + --hash=sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed \ + --hash=sha256:5dbc35d2b5320dc07f25fa31269cfb767471002b17de5eb067d03da68c7cb2db \ + --hash=sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950 \ + --hash=sha256:772235332b5d1024c696f11cea1ae4be7930f0a8b894bb43db14e3f435f1ff7e \ + --hash=sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c \ + --hash=sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa \ + --hash=sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e \ + --hash=sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b \ + --hash=sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9 \ + --hash=sha256:b2200a054ca6d6625c4842fc56a4976a4b47f96b73dbe5538c3f813a80359f47 \ + --hash=sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc \ + --hash=sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5 \ + --hash=sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9 \ + --hash=sha256:d11417d84412f859b722fad0841b3614459ed0047f7542d8362e77884f6b6e8a \ + --hash=sha256:d620900033cc7531e50727c3c8333091df5dd3ffe6d68cdca38c03f5821408d5 \ + --hash=sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b \ + --hash=sha256:dc90147579a905b8635e1b0ec6514967dcb07e6e0d9c42f1477feef14cac23bb + # via mcp +pywin32-ctypes==0.2.3 ; sys_platform == 'win32' \ + --hash=sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8 \ + --hash=sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755 + # via keyring +pyyaml==6.0.3 \ + --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ + --hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \ + --hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \ + --hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \ + --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \ + --hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \ + --hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \ + --hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \ + --hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \ + --hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \ + --hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \ + --hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \ + --hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \ + --hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \ + --hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \ + --hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \ + --hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \ + --hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \ + --hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \ + --hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \ + --hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \ + --hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \ + --hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \ + --hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \ + --hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \ + --hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \ + --hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \ + --hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \ + --hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \ + --hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \ + --hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \ + --hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \ + --hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \ + --hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \ + --hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \ + --hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \ + --hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \ + --hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \ + --hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \ + --hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \ + --hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \ + --hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \ + --hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \ + --hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \ + --hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \ + --hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \ + --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \ + --hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \ + --hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \ + --hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \ + --hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \ + --hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \ + --hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \ + --hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \ + --hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \ + --hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \ + --hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \ + --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \ + --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \ + --hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \ + --hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \ + --hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \ + --hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \ + --hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \ + --hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \ + --hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \ + --hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \ + --hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \ + --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \ + --hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \ + --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \ + --hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \ + --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 + # via + # -c installer/requirements.lock + # fastmcp-slim + # jsonschema-path +referencing==0.37.0 \ + --hash=sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 \ + --hash=sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8 + # via + # -c installer/requirements.lock + # jsonschema + # jsonschema-path + # jsonschema-specifications +rich==15.0.0 \ + --hash=sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb \ + --hash=sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36 + # via + # -c installer/requirements.lock + # cyclopts + # fastmcp-slim + # rich-rst +rich-rst==2.1.0 \ + --hash=sha256:7ecd1343ee12c879d0e7ae74c3eb6d263b023d2929c6d114212eb1fd91057255 \ + --hash=sha256:f4d117b49697f338769759fa5cacf5197da4888b347b9fda2e50aef5cd8d93bd + # via + # -c installer/requirements.lock + # cyclopts +rpds-py==2026.6.3 \ + --hash=sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5 \ + --hash=sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680 \ + --hash=sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9 \ + --hash=sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538 \ + --hash=sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804 \ + --hash=sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf \ + --hash=sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4 \ + --hash=sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97 \ + --hash=sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6 \ + --hash=sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96 \ + --hash=sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a \ + --hash=sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187 \ + --hash=sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975 \ + --hash=sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f \ + --hash=sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703 \ + --hash=sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9 \ + --hash=sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127 \ + --hash=sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f \ + --hash=sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa \ + --hash=sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05 \ + --hash=sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171 \ + --hash=sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba \ + --hash=sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c \ + --hash=sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223 \ + --hash=sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4 \ + --hash=sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885 \ + --hash=sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698 \ + --hash=sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f \ + --hash=sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7 \ + --hash=sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed \ + --hash=sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f \ + --hash=sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf \ + --hash=sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e \ + --hash=sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f \ + --hash=sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24 \ + --hash=sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a \ + --hash=sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41 \ + --hash=sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc \ + --hash=sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d \ + --hash=sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146 \ + --hash=sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e \ + --hash=sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e \ + --hash=sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4 \ + --hash=sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12 \ + --hash=sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7 \ + --hash=sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261 \ + --hash=sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6 \ + --hash=sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5 \ + --hash=sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93 \ + --hash=sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7 \ + --hash=sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda \ + --hash=sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8 \ + --hash=sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342 \ + --hash=sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c \ + --hash=sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb \ + --hash=sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0 \ + --hash=sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77 \ + --hash=sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3 \ + --hash=sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885 \ + --hash=sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826 \ + --hash=sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617 \ + --hash=sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb \ + --hash=sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577 \ + --hash=sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80 \ + --hash=sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e \ + --hash=sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945 \ + --hash=sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90 \ + --hash=sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7 \ + --hash=sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0 \ + --hash=sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140 \ + --hash=sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822 \ + --hash=sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba \ + --hash=sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9 \ + --hash=sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4 \ + --hash=sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a \ + --hash=sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8 \ + --hash=sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf \ + --hash=sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4 \ + --hash=sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324 \ + --hash=sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53 \ + --hash=sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b \ + --hash=sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41 \ + --hash=sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9 \ + --hash=sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca \ + --hash=sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1 \ + --hash=sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d \ + --hash=sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690 \ + --hash=sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107 \ + --hash=sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2 \ + --hash=sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76 \ + --hash=sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d \ + --hash=sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af \ + --hash=sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6 \ + --hash=sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db \ + --hash=sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369 \ + --hash=sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd \ + --hash=sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911 \ + --hash=sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504 \ + --hash=sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a \ + --hash=sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9 \ + --hash=sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13 \ + --hash=sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc \ + --hash=sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278 \ + --hash=sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868 \ + --hash=sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2 \ + --hash=sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd \ + --hash=sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4 \ + --hash=sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6 \ + --hash=sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9 \ + --hash=sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00 \ + --hash=sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f \ + --hash=sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e \ + --hash=sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442 \ + --hash=sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da \ + --hash=sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90 \ + --hash=sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef + # via + # -c installer/requirements.lock + # jsonschema + # referencing +secretstorage==3.5.0 ; sys_platform == 'linux' \ + --hash=sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137 \ + --hash=sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be + # via keyring +sse-starlette==3.4.11 \ + --hash=sha256:1bae716c02f3e6f294be41ff333220692dae7c3cbab077c900f159676719dade \ + --hash=sha256:c7b2244bdff016fe7f64e10075e89a3e6bbf899649cc89b0fe884b5545042453 + # via + # -c installer/requirements.lock + # mcp +starlette==1.6.0 \ + --hash=sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c \ + --hash=sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b + # via + # -c installer/requirements.lock + # -r connector/requirements-test.in + # fastmcp-slim + # mcp + # sse-starlette +typing-extensions==4.16.0 \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \ + --hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5 + # via + # -c installer/requirements.lock + # anyio + # exceptiongroup + # fastmcp-slim + # mcp + # opentelemetry-api + # py-key-value-aio + # pydantic + # pydantic-core + # referencing + # starlette + # typing-inspection +typing-inspection==0.4.4 \ + --hash=sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47 \ + --hash=sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147 + # via + # -c installer/requirements.lock + # mcp + # pydantic + # pydantic-settings +uncalled-for==0.4.0 \ + --hash=sha256:16c4bb3337532e4bd5569adc192285976f3ad5305402256d34c67a12b5c968bd \ + --hash=sha256:335b95bd2422332ec210d518f314a16e4c640921c39fc8bf2ad095bd3538f4af + # via + # -c installer/requirements.lock + # fastmcp-slim +uvicorn==0.52.4 \ + --hash=sha256:73acfee47a0b133c5de13d219492d62d8a31e935f4fe6e41a232451a15379f86 \ + --hash=sha256:f86e41a149d7d05a9969337e3946a9c171c06a5d42680896daaba624aeac8da1 + # via + # -c installer/requirements.lock + # fastmcp-slim + # mcp +watchfiles==1.2.0 \ + --hash=sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9 \ + --hash=sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98 \ + --hash=sha256:027ae72bfdfd254862065d8b3e2a815c6ab9b1853ce41e6648ece84afd34a551 \ + --hash=sha256:03b14855c6f35539e2d95c442ae9530a75762f1e26567152b9ed05f96534a74d \ + --hash=sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7 \ + --hash=sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db \ + --hash=sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69 \ + --hash=sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242 \ + --hash=sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925 \ + --hash=sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f \ + --hash=sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5 \ + --hash=sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5 \ + --hash=sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427 \ + --hash=sha256:11743adfa510bfffebe97659fb280182b5c9b238708f667e866f308c3430dc19 \ + --hash=sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4 \ + --hash=sha256:204f299afcbd65918ab78dbc52626b0ae45e9d8cef403fdbf33ecf9e40eac66e \ + --hash=sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa \ + --hash=sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba \ + --hash=sha256:24b2405c0a46738dd9e1cf7135aa5dbdb9d42d024628651b3b13d5117e99f8df \ + --hash=sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c \ + --hash=sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906 \ + --hash=sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65 \ + --hash=sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c \ + --hash=sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c \ + --hash=sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30 \ + --hash=sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077 \ + --hash=sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374 \ + --hash=sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01 \ + --hash=sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33 \ + --hash=sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831 \ + --hash=sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9 \ + --hash=sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2 \ + --hash=sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b \ + --hash=sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f \ + --hash=sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658 \ + --hash=sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579 \ + --hash=sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5 \ + --hash=sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0 \ + --hash=sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7 \ + --hash=sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666 \ + --hash=sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5 \ + --hash=sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201 \ + --hash=sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103 \ + --hash=sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6 \ + --hash=sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8 \ + --hash=sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1 \ + --hash=sha256:77a0feab9af4c021c581f695258c642b3d10c5fd4c676e33a0d8606425d82631 \ + --hash=sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898 \ + --hash=sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d \ + --hash=sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44 \ + --hash=sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2 \ + --hash=sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5 \ + --hash=sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a \ + --hash=sha256:8c520725602756229f045b032a1ff33d7ef0f7404189d62f6c2438cb6d8ef6a1 \ + --hash=sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b \ + --hash=sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc \ + --hash=sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5 \ + --hash=sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377 \ + --hash=sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8 \ + --hash=sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add \ + --hash=sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281 \ + --hash=sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9 \ + --hash=sha256:a16ffe19bf5cf9f5edaa1ad1dd830c5a816e8feec430c522302ab55483a4b994 \ + --hash=sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0 \ + --hash=sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e \ + --hash=sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0 \ + --hash=sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28 \ + --hash=sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7 \ + --hash=sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55 \ + --hash=sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb \ + --hash=sha256:b62f042afde2dde21ec1d2c1a74361e804673df86f51e418a999c9acfe671b07 \ + --hash=sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb \ + --hash=sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4 \ + --hash=sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0 \ + --hash=sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e \ + --hash=sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4 \ + --hash=sha256:bb68bf4df85abebe5efddc53cf2075520f243a59868d9b3973278b23e76962a9 \ + --hash=sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06 \ + --hash=sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26 \ + --hash=sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7 \ + --hash=sha256:c16cb06dd17d43b9d185094268459eac92c9538356f050e55b54e82cf700e1d4 \ + --hash=sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3 \ + --hash=sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3 \ + --hash=sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838 \ + --hash=sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71 \ + --hash=sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488 \ + --hash=sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717 \ + --hash=sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d \ + --hash=sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44 \ + --hash=sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2 \ + --hash=sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b \ + --hash=sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2 \ + --hash=sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22 \ + --hash=sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6 \ + --hash=sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e \ + --hash=sha256:e1cfd51e97e13ff3bd047c140764d277fc9b95b7cb5da59e46a47d167adab310 \ + --hash=sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165 \ + --hash=sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5 \ + --hash=sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799 \ + --hash=sha256:eb72919d93e3a16fc451d3aa3d4b1698423daca1b382d3d959c9ac51297c12a8 \ + --hash=sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7 \ + --hash=sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379 \ + --hash=sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925 \ + --hash=sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72 \ + --hash=sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4 \ + --hash=sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08 \ + --hash=sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4 + # via + # -c installer/requirements.lock + # fastmcp-slim +websockets==17.0.1 \ + --hash=sha256:02ed63bf26dda9fa27df730a41f6664586c4ee05972c8fb667ce1725b3fd13d3 \ + --hash=sha256:02f0b037a737d0cb0c33866c97bcd1a0b73170dfbf42d69d8fb86f51002fd5ae \ + --hash=sha256:038cfad5d5417f8bb09295abe986029a26d22f34bda622ccc79b670efd4dab56 \ + --hash=sha256:07abc3bd196a48af476a82fd47f3f79a6a3f70937a9f930cef703cfa0c9d83b6 \ + --hash=sha256:07d78a509c3333f5908c83d7f78144ea68a6c9ec28110f5c54d81d8fcdc262c4 \ + --hash=sha256:0b52c76b8a870b141b7ca0705289452183ce7a523101954ccfe29a25986a673f \ + --hash=sha256:10b1587c599fa0f2c89154587c80e0fda98ade6c9fa8c0260a2823fb1800b685 \ + --hash=sha256:10f461191125c63902ea7394ae9e752b1b5785641850c1d365bb30b0f88bc53f \ + --hash=sha256:15920057a6b723f84734f0641403bca163a4b176e5af809ee4f0c4a1e75e9fed \ + --hash=sha256:17ac37716c0244e82c9e384c41653c090b1864c6610224ca3857e7f7b58fce10 \ + --hash=sha256:18ded646ce98cdd3c0235825b3252f1df55765ba49b616bb10282f758667b4d0 \ + --hash=sha256:1b363bfd72a52c0658a3154a4cff219f15a474b35a235057d38853bf151acce7 \ + --hash=sha256:1bdd8c4be420905dd732e00dcd669852d8128cc723efa585a0c0e51adb00a28a \ + --hash=sha256:1d4cf7e8e5b8b1fa40758ac7524843a00237b124ab217e227542cafcfeb7a946 \ + --hash=sha256:1df81d174c1561292de9e40b141cafc04f69077272f6c352afe1d743e20810df \ + --hash=sha256:20a92f78ac8250984ed459faa9ca48c285adbfc0038ddc3fdac6046990a9c9ed \ + --hash=sha256:22bd00f8bae2bccdb5dbe41e20f58ba44ca9fff0b4b561aaf39099c35da762ed \ + --hash=sha256:2437d4ca208cc0f246d3a2297ae7474b4ba18261aaf5b9c79c84c031ecf348e1 \ + --hash=sha256:246927ae9ae06ca0d42a483a4bdb80d4862e1ee5b4cab37c354a5e1ad8356448 \ + --hash=sha256:2503c7e2a5049a12d5dac917a46d5d52591283a766165b8176bb167560421b38 \ + --hash=sha256:2604de7228506b13a44a256a9d223943340c0e725af5d367dc068e192b027761 \ + --hash=sha256:28012a54510fe8301bb893ef143cec30a2780a2d3bc20b7bbdf4379d7a63945d \ + --hash=sha256:2a855b6dfe21c4d3420be265ae031829ba8ba0be0ea350d9f7c3ef30ae63ebe2 \ + --hash=sha256:2abb1ba0a5133b7d2ef3c1c9f4b0c1e8a101012dce0b594ab2b2888d9a64820e \ + --hash=sha256:2b3f3020171202b135ca078e20434977c6b2b02af647130d6980c9e39b9462e3 \ + --hash=sha256:2bc14b481e05e331811108daa1aeb41a5e237a5564ef2f02ec5a356a0f102f78 \ + --hash=sha256:2fa2cb465a131c347ba6717a78c887746e73edb1c131d01c982d6ef0d68b82e0 \ + --hash=sha256:409d93efcaa14f7a99592c5baaef5ec6ca94fba0f5aec1a86f693977c69c9c1c \ + --hash=sha256:41d6aa06b5ab832aee72fedf47a149535b121ac900b6bb4d3fe14712afac9a79 \ + --hash=sha256:49266e4488309b38783257293a38298942b9a03aa106fcb45195377a77c0c1e2 \ + --hash=sha256:4d1d99db29b5444e3982f1ce2ba8a833508ad44b2f1fbd0bd99e81d825c0b461 \ + --hash=sha256:4d41c0a1d47a478bc432b3b9068097bee1ce0c5b19327ea6f75c2ab34ab1f2fb \ + --hash=sha256:5033ffe6804dd53afafa7d08e8c3eef2d2431f34d58ca30507a8442dd04a033a \ + --hash=sha256:53b90c00bc6201ab6695c7ff51a04d0e425514c37515e9eeecd2c1b978ac6c0e \ + --hash=sha256:54cdcaa56f5d3eafd57058f0fa4a3de93a310b43a3c4699f06efc4c0bd054a5a \ + --hash=sha256:5508f38c98ac29def9e747b87543b008a58b075df6da70b2cf2e0b47073d33bb \ + --hash=sha256:55383d8177b3c99fd873ee5db0e0193f4c1dd4a3feaccf1a4a03c1b7cf539cac \ + --hash=sha256:55b12e47dcee83673a40d07686cfb6f9d6dfc285976ade9463f61d2bef3fad22 \ + --hash=sha256:5661f868ef191d33dfc6a0cc7c5b3d495f0cc8bb3f8b30d87bda8755c61c95f5 \ + --hash=sha256:57d2ee9b24b404ce75f3814f92073c0ed88106c950148d2427fe8d25ca254d1f \ + --hash=sha256:599b03beb77633bffc095334338fad79cafc2b01fbd58953838130a9ae967d7b \ + --hash=sha256:5baa9bc0dfbae8c507e51c8cf1b6d4628086f7a87bbd3a9952bd5f035451f1cc \ + --hash=sha256:5f33a649bfcb8312524173cc4bbafa7dbb236e18eee9aa31a1d324ca0ddda28c \ + --hash=sha256:6740be6d1bab69f08ab52cb15b08f76c143b6fe61c580ba62bd929f3ab7a1d42 \ + --hash=sha256:6a434e59962a4fb9016bea327e1d14d6cd67670ecfb8942b4f4a0c24036634ce \ + --hash=sha256:6db9e5bf3649ab506c6ae8a3ac85a00fb1ae3816d75962771b2df8adbc5d40d2 \ + --hash=sha256:6fd88365da261c53d3e943fb37e0d0721b9cde119f6b2e3fc84369b6ab234d63 \ + --hash=sha256:7002d5f9e1c3ddd991cdfdbfee18cc8c8b196b2445022892badacd6cb338bbbc \ + --hash=sha256:70d438268e49f1a4bd096b6b6f7010f3ab48b5db2574dbf7d8c864c46ce7a06a \ + --hash=sha256:72d7f2a5aeb4e82daa4ee18f125b4277f427033359be5c745ad709608446cc2c \ + --hash=sha256:733e3cc7171fa1b899edbe725ef9382d0e960657dc1fd933f3281ae910c01dab \ + --hash=sha256:734d20364dc2cfe03674883cafcf580b6e431c5ce42b476312b9285310230cf9 \ + --hash=sha256:759adeb5b0c5775b563254ec63b5b79089fc0045b479143a0b1b8c0ebaae1253 \ + --hash=sha256:769ce7e2acfd9a89f2bed3a9c0da229459516bbc00bd4c9e2ca492c613ae4861 \ + --hash=sha256:810cb3fb5fa6e447216f4e82d9a85cb8aed0929ae3538153ddfe8a6e3121a58d \ + --hash=sha256:81ce19c6046ace11da7001781be7317bb1dc389f399af4b2ed962190f76f9add \ + --hash=sha256:846a4a8b0833e3cad57523d9e3bd50ec8ea05ab9d06c582f82a1340ba096af5f \ + --hash=sha256:872273e629ca7e3d35f16a2dc6ede84e1d5c831e616b8277de6e4f83114e7c58 \ + --hash=sha256:8848c207049ad49d318e5f64a3d4d7bb189f8328d0d98e65647788f2a085785c \ + --hash=sha256:884af729b8ab50486acd94d9768c2b60914bf39b579ebba0a5cb73bfdfd61fd2 \ + --hash=sha256:8c07f145d0b9e90cbd96035f31fb79199aef4da1872854e36ebeb258e3d57594 \ + --hash=sha256:8cd3369e42c0246afaf9d669cfc19797e3a49e8c0a639544459c57597108b966 \ + --hash=sha256:8e387adb0c692c6b5571bdeafc8ac9d1901ea30f10309134780b16ecd35e6605 \ + --hash=sha256:90246fa9e6cb192a778ce6ce024057ec54317a894db7899c922dcdc1f4cbf6a5 \ + --hash=sha256:90973a3a00f23afdfd1c9b06fb84289bf0220f247ef8a62501a1967c7af54f7b \ + --hash=sha256:9493314a99e599163c854fb5900ad7f7ea38c5cb9d9103aa30b3c6b8181c01fa \ + --hash=sha256:9f7747d3daa41a11f25f7cca5dc988fc51da97b311bed4c9d843860f79779283 \ + --hash=sha256:a39ce3a7b0e6059be093213d637963101380157bcbad355916738fafb490698d \ + --hash=sha256:a60fa1a25cca1bcc2bf87b8d6be37a741f0a3239fb5e9cfb7a37173b68ffcf87 \ + --hash=sha256:a68e604c6d1b0338e46652e2688cbce8096ad9c03548b075fda9e2ea19a9b7dd \ + --hash=sha256:a8af570fc29cd998a921c7131c8ac81d9434466d6d25300cb12a690fb56a8a08 \ + --hash=sha256:aadc298969ad229d8e3029fc5cc751fdad286696230f9cf014e90ff9cd8e6ea0 \ + --hash=sha256:ab56439c9f74c52770690c7b2f616b3bf775cb3920453ee355ac765c032d8bbf \ + --hash=sha256:ab9f962a5b64a5c3c845d556b7dc4e6fb683f7b67179f8205e814bb2e0213ffe \ + --hash=sha256:afbce6e3f0fac32dc87c2a0d84869d1a706460d64f39f3889386413e6e4d3d26 \ + --hash=sha256:b3ff0ad440ad52dda64138f16895f66403f40192365e39b1010e889f289746b0 \ + --hash=sha256:b580794e926cab7ff42ee4371ef14e0b22cb2bb722a607f77769136468f49a3f \ + --hash=sha256:b85b960a4507b0714c0a1246d031be9118d908ee974dc085257297a955205f1d \ + --hash=sha256:b98860aefbd3d9bc8e3c7f0eefb83b11142b16110739c68cd33d3b4d6e84e536 \ + --hash=sha256:bb31f42ea095ea826463c770829aa188a86c9a5c976b1467cbbf583c811de833 \ + --hash=sha256:bc0bca48ba24c6c866847fd20478a51dd547fa0ad258dab9615c414ec534bbc0 \ + --hash=sha256:bd1470d2c53fe53269bf5619da7725d30dd9b9693f1689f7a85eab8dea734442 \ + --hash=sha256:c09e097d0e46e3c289bedab9a475ae344b70c30ff5646e46af22b4e6fdc97b21 \ + --hash=sha256:c1bec5d6a19f5fbe87e4940739cfc65e7bb53d8b353e1029b8037a1653b321bc \ + --hash=sha256:c1c118a6b0e25bfc9a6802075d748fa6321714ffbdf3c88d29d9a0e3c7386c75 \ + --hash=sha256:c23e532c8a2325a1e7486de8763a60dc43e83f01bcaeca07e3ba79652c156db1 \ + --hash=sha256:c356dbddab0a529ed7574f78f559d75a223735c321c28f6f587fbf02b11ed301 \ + --hash=sha256:c38515cb54902f7e97d0239e81ef46c4444f9475f4807fb9bbdb789b4089abcf \ + --hash=sha256:c395bda8e7d8f51a02e80261fb57127979e5c472675d9a96b2860619ad47da48 \ + --hash=sha256:c6be9cba65c65cc76dfa3d4619e359ff02a4476c74e179b215236c11a0b32345 \ + --hash=sha256:cd526c8228e759c1006c4b7c9ac71dc4e925ced1a6a6a5a8e94643709738f63e \ + --hash=sha256:cddc675ec31bca65473321f9a9794e488b43b3b8de5d02c8ef4810c5d5792163 \ + --hash=sha256:cffc84ddec6da7f447677266fee2a3c40ecc78172f00752aa1150b8a8d65df1d \ + --hash=sha256:d41e9845514754a42d1d83b2fca9d27fee2ca7b3b0bee6843ba5a9bb2b6e25ac \ + --hash=sha256:d69fd559f9f0e8a52d2fce6f04ee143f86e70df0a189cd95164eddac599e810f \ + --hash=sha256:d7d72843691f50b91127c50688df10cb72ec6f4c4b1d7e2c11ab33b16acf8e51 \ + --hash=sha256:d9aac6081513f02eac3f8caace800dbfc5c608b69e4a7bef69e414eabfc95aa1 \ + --hash=sha256:dbfae8e75b342e31fc6fd1a8bbb393b7cbb91d6cfd581650300a94381e7b7e2b \ + --hash=sha256:e8208f2729cba030ff872a92064c97584eeb9502f53d32a05a0f05d5a17ca6c6 \ + --hash=sha256:e95e321d0d763f2b6633512605f6112ebd70d5746f3ce05c941909d4a25233f2 \ + --hash=sha256:e98ec9ec61cce5bc4b8b218322ad090b0994eb060bb04da704c62ef0a3d864e6 \ + --hash=sha256:eab6de8a98b9a7772cf686d00b4de439fc7efb8ab05ae106ef227291d06f87c5 \ + --hash=sha256:efe0ae052a8d023b87198921e8a7ce1dc7768816bcd2fbc20df171ac73a04891 \ + --hash=sha256:f11a398d8170b7ac5000baf7f258dcda579ef3ea744e0cc6a165e0dfbc0d3198 \ + --hash=sha256:f3fd9a1f87f8f0f3f8e9f9bd0195f7516562d13f5b178db8c5784d1f60b60bed \ + --hash=sha256:f47b0815af3948ec6a440b3afa02f05b18cc0939549e91b5c677b5d9c2c8472a \ + --hash=sha256:f991247276797d0c61ab7770bc9791eadc16f683b4d83517f624932adc1a8bab \ + --hash=sha256:ffad64ce7ad3703d652a3fd9af26238377d24ce52c6ad8ff35d26d82f61f493f + # via + # -c installer/requirements.lock + # fastmcp-slim diff --git a/connector/test_connector.py b/connector/test_connector.py index ed0fd1b..f41332e 100644 --- a/connector/test_connector.py +++ b/connector/test_connector.py @@ -187,23 +187,29 @@ def test_failure_classifier_is_payload_free_and_actionable(self): def test_slow_repository_does_not_block_independent_computer_request(self): import concurrent.futures import threading - import time - entered = threading.Event() + entered, release = threading.Event(), threading.Event() def slow(*args): entered.set() - time.sleep(0.6) + if not release.wait(10): + raise TimeoutError("repository fixture was not released") return [] self.server.tool(name="computer_probe")(lambda: "probe completed") with patch("borg_context_server._repos_matching", slow), concurrent.futures.ThreadPoolExecutor() as pool: pending = pool.submit(self.call, "borg_projects", {}) - self.assertTrue(entered.wait(2)) - started = time.monotonic() - result = self.call("computer_probe", {}) - self.assertFalse(result.get("isError", False)) - self.assertLess(time.monotonic() - started, 0.35) - self.assertFalse(pending.result().get("isError", False)) + try: + self.assertTrue(entered.wait(3)) + probe = pool.submit(self.call, "computer_probe", {}) + result = probe.result(timeout=3) + self.assertFalse(result.get("isError", False)) + # Prove causal independence, not a wall-clock race against a + # sleeping fixture on a contended developer or CI machine. + self.assertFalse(release.is_set()) + self.assertFalse(pending.done()) + finally: + release.set() + self.assertFalse(pending.result(timeout=3).get("isError", False)) def test_repository_inspection_honors_total_budget(self): import time @@ -231,7 +237,9 @@ def test_private_configuration_denies_world_readable_and_owner_fallback(self): config = self.root / "config.json" doc = {"version": 1, "access_mode": "owner_all", "mem0_principal": PRINCIPAL, "allowed_scopes": ["*"], "inbound_authorization_file": str(self.inbound), - "mem0_token_file": str(self.upstream), "project_roots": [str(self.project_root)]} + "mem0_token_file": str(self.upstream), "project_roots": [str(self.project_root)], + "mem0_url": "http://127.0.0.1:8765/mcp", "default_scope": "personal:example", + "state_root": str(self.root)} config.write_text(json.dumps(doc)) config.chmod(0o644) with self.assertRaises(ValueError): diff --git a/connector/test_job_portability.py b/connector/test_job_portability.py new file mode 100644 index 0000000..ddd985e --- /dev/null +++ b/connector/test_job_portability.py @@ -0,0 +1,56 @@ +"""Portable durable-job launch with synthetic commands and isolated stores.""" +from pathlib import Path +import tempfile +import time +import unittest +from unittest.mock import patch + +from fastmcp.exceptions import ToolError +from job_tools import JobStore, _job_command + + +class JobShellTests(unittest.TestCase): + def test_preserves_zsh_login_behavior_when_available(self): + with patch('job_tools.os.path.isfile', return_value=True), patch('job_tools.os.access', return_value=True): + self.assertEqual(_job_command('printf unchanged'), ['/bin/zsh', '-lc', 'printf unchanged']) + + def test_posix_fallback_without_zsh(self): + with patch('job_tools.os.path.isfile', side_effect=lambda path: path == '/bin/sh'), patch('job_tools.os.access', return_value=True): + self.assertEqual(_job_command("printf '%s' 'two words'"), + ['/bin/sh', '-c', "printf '%s' 'two words'"]) + + def test_nonexecutable_zsh_is_not_selected(self): + with patch('job_tools.os.path.isfile', return_value=True), patch('job_tools.os.access', side_effect=lambda path, mode: path == '/bin/sh'): + self.assertEqual(_job_command('true')[0], '/bin/sh') + + def test_missing_shell_refuses_before_job_artifacts_or_spawn(self): + with tempfile.TemporaryDirectory() as directory: + store = JobStore(Path(directory)) + with patch('job_tools.os.path.isfile', return_value=False), patch('job_tools.subprocess.Popen') as spawn: + with self.assertRaisesRegex(ToolError, 'capability unavailable'): + store.start('true') + spawn.assert_not_called() + self.assertEqual(list(store.jobs.iterdir()), []) + + def test_real_posix_fallback_produces_durable_output(self): + with tempfile.TemporaryDirectory() as directory: + store = JobStore(Path(directory)) + original = __import__('os').path.isfile + try: + with patch('job_tools.os.path.isfile', side_effect=lambda path: False if path == '/bin/zsh' else original(path)): + job = store.start("printf '%s' 'portable job'", timeout_ms=0) + deadline = time.monotonic() + 5 + status = store.status(job['job_id']) + while status['state'] == 'running' and time.monotonic() < deadline: + time.sleep(0.01) + status = store.status(job['job_id']) + self.assertEqual(status['state'], 'succeeded') + self.assertEqual(status['returncode'], 0) + self.assertEqual(store.read_output(job['job_id'], stream='stdout')['output'], 'portable job') + self.assertNotIn('command', status) + finally: + store.close_all() + + +if __name__ == '__main__': + unittest.main() diff --git a/connector/test_operation_diagnostics.py b/connector/test_operation_diagnostics.py new file mode 100644 index 0000000..f67aadb --- /dev/null +++ b/connector/test_operation_diagnostics.py @@ -0,0 +1,272 @@ +"""Effect-safety regressions with disposable files and synthetic fleet peers.""" +import asyncio +import json +from pathlib import Path +from types import SimpleNamespace +import tempfile +import threading +import unittest +from unittest.mock import AsyncMock, patch +import uuid + +from fastmcp.exceptions import ToolError +from fastmcp.tools import ToolResult +from mcp.types import CallToolResult, TextContent +from computer_tools import BoundaryMiddleware, classify_failure +from fleet_tools import Fleet, FleetPreflightError +from operation_diagnostics import for_call, sanitize +from operation_receipts import OperationLedger + + +class DiagnosticTests(unittest.TestCase): + def test_closed_schema_drops_payloads_and_recomputes_retryability(self): + source = {"phase": "dispatch", "cause": "permission_denied", "effect_state": "outcome_unknown", + "retryable": True, "error_text": "PRIVATE_ERROR", "arguments": {"command": "PRIVATE_COMMAND"}, + "target": {"host": "alpha", "tool": "computer_write_file", "home": "PRIVATE_HOME", + "instance_id": str(uuid.uuid4()), "receipt_id": "PRIVATE_RECEIPT"}} + result = sanitize(source) + self.assertFalse(result["retryable"]) + self.assertFalse(result["retry_without_change"]) + self.assertNotIn("PRIVATE_", json.dumps(result)) + self.assertEqual(result["target"]["host"], "alpha") + self.assertNotIn("home", result["target"]) + + def test_invalid_field_types_fail_closed(self): + result = sanitize({"phase": [], "effect_state": {}, "cause": [], "target": "bad"}) + self.assertEqual(result["effect_state"], "outcome_unknown") + self.assertFalse(result["retryable"]) + self.assertNotIn("target", result) + + def test_only_known_predispatch_transient_failures_are_retryable(self): + for code in ["busy", "timeout", "provider_unavailable", "rate_limited"]: + self.assertTrue(sanitize({"effect_state": "not_started", "cause": code})["retryable"]) + self.assertFalse(sanitize({"effect_state": "outcome_unknown", "cause": code})["retryable"]) + for code in ["identity_mismatch", "bad_request", "permission_denied", "downstream_error", None]: + self.assertFalse(sanitize({"effect_state": "not_started", "cause": code})["retryable"]) + + def test_exception_type_classifies_empty_timeout_and_connection_errors(self): + self.assertEqual(classify_failure(TimeoutError()), "timeout") + self.assertEqual(classify_failure(ConnectionRefusedError()), "provider_unavailable") + self.assertEqual(classify_failure(PermissionError()), "permission_denied") + + def test_nonfleet_calls_do_not_capture_arbitrary_target_arguments(self): + result = for_call("computer_start_process", {"host": "alpha", "tool": "invented", "command": "PRIVATE_COMMAND"}, + phase="dispatch", effect_state="outcome_unknown") + self.assertNotIn("target", result) + self.assertNotIn("PRIVATE_COMMAND", json.dumps(result)) + + def test_legacy_receipt_remains_readable(self): + with tempfile.TemporaryDirectory() as directory: + ledger = OperationLedger(Path(directory)) + row = ledger.start("computer_write_file", "a" * 64) + ledger.finish(row["receipt_id"], "succeeded") + old = ledger.get(row["receipt_id"]) + old["version"] = 1 + old.pop("diagnostics") + ledger._write(old) + reopened = OperationLedger(Path(directory)) + self.assertEqual(reopened.get(row["receipt_id"]), old) + + def test_restart_preserves_target_and_forces_unknown_retry_unsafe(self): + with tempfile.TemporaryDirectory() as directory: + ledger = OperationLedger(Path(directory)) + detail = for_call("fleet_call", {"host": "alpha", "tool": "computer_write_file"}, + phase="admission", effect_state="not_started", cause="busy") + row = ledger.start("fleet_call", "a" * 64, detail) + row["pid"] = 99999999 + ledger._write(row) + reopened = OperationLedger(Path(directory)) + saved = reopened.get(row["receipt_id"]) + self.assertEqual(saved["state"], "outcome_unknown") + self.assertEqual(saved["diagnostics"]["target"]["host"], "alpha") + self.assertEqual(saved["diagnostics"]["cause"], "process_interrupted") + self.assertFalse(saved["diagnostics"]["retryable"]) + + def test_unknown_terminal_state_overrides_safe_retry_claim(self): + with tempfile.TemporaryDirectory() as directory: + ledger = OperationLedger(Path(directory)) + row = ledger.start("fleet_call", "a" * 64) + result = ledger.finish(row["receipt_id"], "outcome_unknown", "timeout", + {"effect_state": "not_started", "cause": "timeout", "retryable": True}) + self.assertFalse(result["diagnostics"]["retryable"]) + self.assertEqual(result["diagnostics"]["effect_state"], "outcome_unknown") + + def test_terminal_success_cannot_be_replaced_by_cancellation(self): + with tempfile.TemporaryDirectory() as directory: + ledger = OperationLedger(Path(directory)) + row = ledger.start("fleet_call", "a" * 64) + success = ledger.finish(row["receipt_id"], "succeeded") + after = ledger.finish(row["receipt_id"], "outcome_unknown", "request_cancelled") + self.assertEqual(after, success) + + +class DiagnosticFixture(unittest.IsolatedAsyncioTestCase): + async def asyncSetUp(self): + self.temp = tempfile.TemporaryDirectory(dir=Path.home()) + self.root = Path(self.temp.name) + self.addCleanup(self.temp.cleanup) + self.ledger = OperationLedger(self.root / "operations") + self.middleware = BoundaryMiddleware(lambda: None, SimpleNamespace( + inbound_authorization_file=self.root / "absent-inbound", mem0_token_file=self.root / "absent-upstream", + computer={}), self.ledger) + self.context = SimpleNamespace(message=SimpleNamespace(name="computer_write_file", arguments={})) + + +class MiddlewareTests(DiagnosticFixture): + async def test_exception_after_effect_is_not_safe_to_repeat(self): + target = self.root / "effect" + async def action(_): + target.write_text("already changed") + raise ToolError("permission denied after first stage") + with self.assertRaises(ToolError): + await self.middleware.on_call_tool(self.context, action) + self.assertEqual(target.read_text(), "already changed") + saved = self.ledger.recent(1)[0] + self.assertEqual(saved["state"], "outcome_unknown") + self.assertEqual(saved["diagnostics"]["cause"], "permission_denied") + self.assertFalse(saved["diagnostics"]["retryable"]) + + async def test_returned_error_after_dispatch_is_also_unknown(self): + result = ToolResult(content="permission denied", is_error=True) + returned = await self.middleware.on_call_tool(self.context, lambda _: asyncio.sleep(0, result=result)) + saved = self.ledger.get(returned.meta["borg_operation_receipt"]) + self.assertEqual(saved["state"], "outcome_unknown") + self.assertEqual(returned.meta["borg_failure"]["effect_state"], "outcome_unknown") + self.assertFalse(returned.meta["borg_failure"]["retryable"]) + + async def test_admission_failure_has_no_effect_and_no_call(self): + action = AsyncMock() + with patch.object(self.middleware.scheduler, "run", AsyncMock(side_effect=ToolError("BORG_BUSY: queue is full"))): + with self.assertRaises(ToolError): + await self.middleware.on_call_tool(self.context, action) + action.assert_not_called() + saved = self.ledger.recent(1)[0] + self.assertEqual(saved["state"], "failed") + self.assertEqual(saved["diagnostics"]["effect_state"], "not_started") + self.assertTrue(saved["diagnostics"]["retryable"]) + + async def test_cancellation_during_starting_receipt_is_finalized_without_execution(self): + entered, release = threading.Event(), threading.Event() + original = self.ledger.start + def blocked(*args): + row = original(*args) + entered.set() + if not release.wait(3): + raise RuntimeError("fixture not released") + return row + action = AsyncMock() + with patch.object(self.ledger, "start", side_effect=blocked): + pending = asyncio.create_task(self.middleware.on_call_tool(self.context, action)) + try: + self.assertTrue(await asyncio.to_thread(entered.wait, 1)) + pending.cancel() + await asyncio.sleep(0) + finally: + release.set() + with self.assertRaises(asyncio.CancelledError): + await pending + action.assert_not_called() + saved = self.ledger.recent(1)[0] + self.assertEqual(saved["state"], "failed") + self.assertEqual(saved["diagnostics"]["effect_state"], "not_started") + + async def test_local_fleet_preflight_refusal_is_distinct_from_remote_error_words(self): + self.context.message.name = "fleet_call" + self.context.message.arguments = {"host": "alpha", "tool": "computer_write_file", "arguments": {}} + action = AsyncMock(side_effect=FleetPreflightError("identity_mismatch")) + with self.assertRaises(ToolError): + await self.middleware.on_call_tool(self.context, action) + saved = self.ledger.recent(1)[0] + self.assertEqual(saved["state"], "failed") + self.assertEqual(saved["diagnostics"]["cause"], "identity_mismatch") + self.assertEqual(saved["diagnostics"]["target"], {"host": "alpha", "tool": "computer_write_file"}) + self.assertFalse(saved["diagnostics"]["retryable"]) + + +class FleetDiagnosticTests(DiagnosticFixture): + async def setup_fleet(self, mode): + identity = {"instance_id": str(uuid.uuid4()), "server_generation": str(uuid.uuid4()), + "owner": "alpha", "home": str(self.root)} + row = {"id": "alpha", "ssh_alias": "alpha", "enabled": True, + **{k: identity[k] for k in ("instance_id", "owner", "home")}} + registry = self.root / "registry.json" + registry.write_text(json.dumps({"schema": "borg-fleet/v1", "hosts": [row]})) + registry.chmod(0o600) + calls = [] + target_receipt = str(uuid.uuid4()) + class Peer: + async def __aenter__(self): return self + async def __aexit__(self, *args): pass + async def close(self): pass + async def call_tool(self, *args, **kwargs): + if mode == "preflight_timeout": raise TimeoutError() + if mode == "unreachable": raise ConnectionRefusedError() + actual = dict(identity) + if mode == "identity_mismatch": actual["instance_id"] = str(uuid.uuid4()) + return SimpleNamespace(structured_content=actual) + async def list_tools(self, **kwargs): + return [SimpleNamespace(name="computer_write_file", model_dump=lambda **kw: {"name": "computer_write_file"})] + async def call_tool_mcp(self, *args, **kwargs): + calls.append(args) + if mode == "lost_response": raise TimeoutError() + return CallToolResult(content=[TextContent(type="text", text="permission denied" if mode == "target_error" else "ok")], + isError=mode == "target_error", _meta={"borg_operation_receipt": target_receipt}) + fleet = Fleet(registry, lambda _: Peer()) + self.addAsyncCleanup(fleet.close) + self.context.message.name = "fleet_call" + self.context.message.arguments = {"host": "alpha", "tool": "computer_write_file", "arguments": {}} + async def action(_): + return await fleet.fleet_call("alpha", "computer_write_file", {}) + return action, calls, identity, target_receipt + + async def test_timeout_before_dispatch_has_typed_durable_target(self): + action, calls, _, _ = await self.setup_fleet("preflight_timeout") + result = await self.middleware.on_call_tool(self.context, action) + saved = self.ledger.get(result.meta["borg_operation_receipt"]) + self.assertEqual(calls, []) + self.assertEqual(saved["failure_code"], "timeout") + self.assertEqual(saved["diagnostics"]["effect_state"], "not_started") + self.assertTrue(saved["diagnostics"]["retryable"]) + self.assertEqual(saved["diagnostics"]["target"]["host"], "alpha") + + async def test_lost_response_records_unknown_and_never_replays(self): + action, calls, identity, _ = await self.setup_fleet("lost_response") + result = await self.middleware.on_call_tool(self.context, action) + saved = self.ledger.get(result.meta["borg_operation_receipt"]) + self.assertEqual(len(calls), 1) + self.assertEqual(saved["failure_code"], "timeout") + self.assertEqual(saved["diagnostics"]["phase"], "dispatch") + self.assertEqual(saved["state"], "outcome_unknown") + self.assertFalse(saved["diagnostics"]["retryable"]) + self.assertEqual(saved["diagnostics"]["target"]["instance_id"], identity["instance_id"]) + + async def test_identity_mismatch_cannot_dispatch_or_suggest_blind_retry(self): + action, calls, _, _ = await self.setup_fleet("identity_mismatch") + result = await self.middleware.on_call_tool(self.context, action) + saved = self.ledger.get(result.meta["borg_operation_receipt"]) + self.assertEqual(calls, []) + self.assertEqual(saved["failure_code"], "identity_mismatch") + self.assertEqual(saved["diagnostics"]["effect_state"], "not_started") + self.assertFalse(saved["diagnostics"]["retryable"]) + + async def test_native_target_receipt_is_preserved_without_home_or_arguments(self): + action, calls, identity, target_receipt = await self.setup_fleet("success") + result = await self.middleware.on_call_tool(self.context, action) + saved = self.ledger.get(result.meta["borg_operation_receipt"]) + self.assertEqual(saved["state"], "succeeded") + self.assertEqual(saved["diagnostics"]["target"]["receipt_id"], target_receipt) + self.assertEqual(saved["diagnostics"]["target"]["server_generation"], identity["server_generation"]) + self.assertNotIn(str(self.root), json.dumps(saved)) + + async def test_target_permission_error_is_not_mistaken_for_preflight_refusal(self): + action, calls, _, _ = await self.setup_fleet("target_error") + result = await self.middleware.on_call_tool(self.context, action) + saved = self.ledger.get(result.meta["borg_operation_receipt"]) + self.assertEqual(len(calls), 1) + self.assertEqual(saved["state"], "outcome_unknown") + self.assertEqual(saved["failure_code"], "permission_denied") + self.assertFalse(saved["diagnostics"]["retryable"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/connector/test_recall_policy.py b/connector/test_recall_policy.py new file mode 100644 index 0000000..95b6edd --- /dev/null +++ b/connector/test_recall_policy.py @@ -0,0 +1,176 @@ +"""Synthetic retrieval controls; no live corpus reads, writes, or identity claims.""" +import copy +from contextlib import asynccontextmanager +from pathlib import Path +from types import SimpleNamespace +import unittest +from unittest.mock import AsyncMock, patch + +from fastmcp.exceptions import ToolError +import borg_context_server as server +from recall_policy import exact_terms, select_memories, unavailable_retrieval + + +def row(text, score=0.7, **extra): + return {"id": "fixture", "memory": text, "score": score, **extra} + + +class SelectionTests(unittest.TestCase): + def test_absent_opaque_identifier_abstains_despite_high_scores(self): + values = [row("Unrelated hexadecimal source", 0.95), row("Unrelated audit state", 0.99)] + kept, result = select_memories("fixture_82f97a6d4b1c_missing_identifier", values, 6) + self.assertEqual(kept, []) + self.assertEqual(result["result_floor"], "sparse") + self.assertEqual(result["retrieval"]["omitted"]["exact_mismatch"], 2) + self.assertEqual(result["retrieval"]["coverage"], "upstream_candidate_pool_only") + + def test_full_name_requires_explicit_quotes_not_capitalization_guessing(self): + values = [row("Ada Another leads sales", 0.99), row("Another Example leads sales", 0.98), + row("Ada Example leads operations", 0.08)] + kept, result = select_memories('"Ada Example" role', values, 6) + self.assertEqual([r["memory"] for r in kept], ["Ada Example leads operations"]) + self.assertEqual(result["retrieval"]["match_policy"], "literal_terms") + self.assertEqual(exact_terms("Ada Example role"), []) + + def test_identifier_prefix_is_not_identity(self): + token = "0123456789abcdef" + values = [row(token + "a"), row("f" + token), row("prefix_" + token), row("Commit " + token)] + kept, _ = select_memories(token, values, 6) + self.assertEqual(kept, values[-1:]) + + def test_identifier_suffix_and_native_row_id(self): + identifier = "0123456789abcdef" + values = [row(identifier + "-suffix"), row(identifier + ":suffix"), + row("Native record without the ID in its text", id=identifier)] + kept, _ = select_memories(identifier, values, 6) + self.assertEqual(kept, values[-1:]) + + def test_opaque_id_allows_sentence_punctuation_but_not_suffixes(self): + identifier = "0123456789abcdef" + values = [row("The commit is " + identifier + "."), + row("The commit is (" + identifier + "), confirmed."), + row(identifier + ".different"), row(identifier + "-different")] + kept, _ = select_memories(identifier, values, 6) + self.assertEqual(kept, values[:2]) + + def test_oversized_numeric_score_is_filtered_without_overflow(self): + kept, result = select_memories("general", [row("invalid score", 10 ** 1000)], 6) + self.assertEqual(kept, []) + self.assertEqual(result["retrieval"]["omitted"]["invalid_score"], 1) + + def test_uuid_is_opaque_but_dates_and_short_codes_are_not(self): + identifier = "12345678-1234-1234-1234-123456789abc" + self.assertEqual(exact_terms(identifier), [identifier]) + for query in ["2026-09-24", "test v1.2", "HTTP 404", "issue 123", "epoch_mismatch"]: + self.assertEqual(exact_terms(query), [], query) + + def test_semantic_candidates_preserve_valid_low_scores_and_order(self): + values = [row("A vehicle for going places", 0.08), row("An automobile", -0.1), + row("Legacy score missing", None)] + kept, result = select_memories("transportation", values, 3) + self.assertEqual(kept, values) + self.assertEqual(result["retrieval"]["status"], "candidates") + self.assertEqual(result["retrieval"]["match_policy"], "semantic_candidates") + + def test_whole_record_junk_is_withheld_but_discussion_is_retained(self): + values = [row("The tool used is tool.", 1.0), row(" THE tool\nused is tool. "), + row("The tool is called 'tool'."), + row('The extractor must reject "The tool used is tool." as vacuous.'), + row("The file tool is an executable that accepts a path.")] + kept, result = select_memories("extraction quality", values, 6) + self.assertEqual(kept, values[3:]) + self.assertEqual(result["retrieval"]["omitted"]["vacuous"], 3) + + def test_nonfinite_and_malformed_candidates_are_explicit(self): + values = [None, {}, row(""), row("NaN", float("nan")), row("Inf", float("inf")), + row("Boolean", True), row("String score", "1.0"), row("Valid")] + kept, result = select_memories("general", values, 6) + self.assertEqual(kept, values[-1:]) + self.assertEqual(result["retrieval"]["omitted"]["malformed"], 3) + self.assertEqual(result["retrieval"]["omitted"]["invalid_score"], 4) + + def test_bad_response_shape_is_not_an_empty_success(self): + for value in ({"results": []}, None, "bad", ()): + with self.subTest(value=value), self.assertRaises(ToolError): + select_memories("general", value, 6) + + def test_exact_phrases_are_literal_whitespace_normalized_and_unicode_safe(self): + values = [row("Élodie Example owns a.*b"), row("Elodie Example owns axxb")] + kept, _ = select_memories('"Élodie Example" "a.*b"', values, 6) + self.assertEqual(kept, values[:1]) + + def test_surname_prefix_does_not_satisfy_phrase(self): + values = [row("Ada Examples works here"), row("Ada Example works here")] + kept, _ = select_memories('"Ada Example"', values, 6) + self.assertEqual(kept, values[1:]) + + def test_all_explicit_terms_must_match(self): + values = [row("alpha beta"), row("alpha"), row("beta")] + kept, _ = select_memories('"alpha" and "beta"', values, 6) + self.assertEqual(kept, values[:1]) + + def test_no_mutation_and_honest_truncation_counts(self): + values = [row("candidate " + str(i)) for i in range(20)] + before = copy.deepcopy(values) + kept, result = select_memories("general", values, 100) + self.assertEqual(len(kept), 12) + self.assertEqual(result["retrieval"]["limit_omitted"], 8) + self.assertEqual(values, before) + + def test_empty_is_distinct_from_unavailable(self): + kept, result = select_memories("general", [], 6) + self.assertEqual(kept, []) + self.assertEqual(result["retrieval"]["candidate_count"], 0) + self.assertEqual(result["retrieval"]["status"], "sparse") + self.assertEqual(unavailable_retrieval()["retrieval"]["status"], "unavailable") + self.assertIsNone(unavailable_retrieval()["retrieval"]["candidate_count"]) + + +class PublicBoundaryTests(unittest.IsolatedAsyncioTestCase): + def setUp(self): + self.context = server.BorgContext(SimpleNamespace(project_roots=(Path("/example/projects"),))) + @asynccontextmanager + async def upstream(): + yield None, None + self.context.upstream = upstream + + async def test_search_and_project_share_exact_selection_and_legacy_fields(self): + values = [row("Ada Another leads sales", 0.99), row("Ada Example leads operations", 0.1)] + with patch.object(server, "authorize"), patch.object(server, "mem0_call", AsyncMock(return_value=values)) as upstream, patch.object(server, "_repos_matching", return_value=[]): + search = await self.context.borg_search('"Ada Example" role', 6) + project = await self.context.borg_project_context("demo", '"Ada Example" role', 6) + self.assertEqual(search["results"], project["recalled_context"]) + self.assertEqual(search["result_floor"], project["result_floor"]) + self.assertEqual(search["retrieval"], project["retrieval"]) + self.assertEqual(search["results"][0]["authority"], "candidate") + self.assertIn("source_notice", search) + self.assertEqual(project["memory_status"], "PASS") + self.assertIn("live_repositories", project) + self.assertEqual(upstream.call_args_list[0].args[2]["limit"], 24) + + async def test_upstream_outage_is_not_successful_sparse_recall(self): + with patch.object(server, "authorize"), patch.object(server, "mem0_call", AsyncMock(side_effect=ToolError("fixture unavailable"))), patch.object(server, "_repos_matching", return_value=[]): + with self.assertRaises(ToolError): + await self.context.borg_search("general") + project = await self.context.borg_project_context("demo") + self.assertEqual(project["memory_status"], "UNAVAILABLE") + self.assertEqual(project["result_floor"], "unavailable") + self.assertIsNone(project["retrieval"]["candidate_count"]) + + async def test_wrong_identity_still_fails_closed(self): + with patch.object(server, "authorize"), patch.object(server, "mem0_call", AsyncMock(side_effect=ToolError("identity or scope differs"))), patch.object(server, "_repos_matching") as repos: + with self.assertRaises(ToolError): + await self.context.borg_project_context("demo") + repos.assert_not_called() + + async def test_junk_does_not_refill_requested_slots(self): + values = [row("The tool used is tool.", 1.0)] * 12 + with patch.object(server, "mem0_call", AsyncMock(return_value=values)): + result = await self.context.borg_search("tool", 6) + self.assertEqual(result["results"], []) + self.assertEqual(result["result_floor"], "sparse") + self.assertEqual(result["retrieval"]["omitted"]["vacuous"], 12) + + +if __name__ == "__main__": + unittest.main() diff --git a/connector/test_resilience.py b/connector/test_resilience.py index f624ab1..9b7826e 100644 --- a/connector/test_resilience.py +++ b/connector/test_resilience.py @@ -271,10 +271,10 @@ def blocked_write(row): raise RuntimeError("test finalizer was not released") original_write(row) - def finish(receipt_id, state, failure_code=None): + def finish(receipt_id, state, failure_code=None, diagnostics=None): if state == "outcome_unknown": cancelled_finalizer.set() - return original_finish(receipt_id, state, failure_code) + return original_finish(receipt_id, state, failure_code, diagnostics) result = SimpleNamespace(is_error=False, meta=None, model_dump=lambda: {"output": "ok"}) with patch.object(ledger, "_write", side_effect=blocked_write), patch.object(ledger, "finish", side_effect=finish):