A declarative, hermetic harness for Nix-defined agents.
0

Configure Feed

Select the types of activity you want to include in your feed.

chore: bootstrap project with design plan and capability manifest

author
Aly Raffauf
date (Jun 26, 2026, 7:25 PM -0400) commit dd55ca40
+452
+19
.gitignore
··· 1 + # Local environment / secrets — never commit (direnv loads these locally) 2 + .envrc 3 + .env 4 + .env.* 5 + 6 + # Python 7 + __pycache__/ 8 + *.py[cod] 9 + .venv/ 10 + .pytest_cache/ 11 + .ruff_cache/ 12 + .mypy_cache/ 13 + 14 + # Local harness audit logs 15 + .tartarus/ 16 + 17 + # Nix 18 + result 19 + result-*
+1
.python-version
··· 1 + 3.13
+20
pyproject.toml
··· 1 + [project] 2 + name = "tartarus-nix" 3 + version = "0.1.0" 4 + description = "A Nix-defined containment runtime for auditable agents." 5 + readme = "README.md" 6 + requires-python = ">=3.13" 7 + dependencies = [ 8 + "httpx>=0.28.1", 9 + ] 10 + 11 + [dependency-groups] 12 + dev = [ 13 + "pytest>=9.1.1", 14 + "ruff>=0.15.20", 15 + "ty>=0.0.55", 16 + ] 17 + 18 + [tool.pytest.ini_options] 19 + pythonpath = ["."] 20 + testpaths = ["tests"]
+1
tartarus/__init__.py
··· 1 + """Tartarus: a Nix-defined containment runtime for auditable agents."""
+131
tartarus/manifest.py
··· 1 + """Manifest data types and provider-neutral tool projection helpers. 2 + 3 + The tools the model sees and the capabilities the broker enforces are derived 4 + from the same source: `build_manifest` projects every non-deny capability into a 5 + tool. 6 + """ 7 + 8 + from typing import Any 9 + 10 + from dataclasses import dataclass, field 11 + 12 + 13 + @dataclass(frozen=True) 14 + class Grant: 15 + """The host reach a capability opens. Empty means "nothing beyond the shell".""" 16 + 17 + package_bins: list[str] = field(default_factory=list) 18 + allowed_hosts: list[str] = field(default_factory=list) 19 + writable: list[str] = field(default_factory=list) 20 + unrestricted: bool = False 21 + # The store path of the `closureInfo` `store-paths` file for this grant's 22 + # packages (emitted by Nix). `closure_paths` is its realized contents — the 23 + # exact store paths the jail binds, so the capability reaches its declared 24 + # closure and nothing else. Populated after realization (manifest_loader). 25 + closure_file: str = "" 26 + closure_paths: list[str] = field(default_factory=list) 27 + 28 + 29 + @dataclass(frozen=True) 30 + class Param: 31 + type: str # JSON Schema scalar: "string" | "integer" | "boolean" | "array" 32 + description: str 33 + required: bool = False 34 + enum: list[Any] | None = None 35 + 36 + 37 + @dataclass(frozen=True) 38 + class Capability: 39 + name: str 40 + description: str 41 + policy: str # "auto" | "ask-once" | "ask-always" | "deny" 42 + params: dict[str, Param] 43 + grants: Grant 44 + runner: str 45 + # Per-capability wall-clock budget in seconds. None means the capability runs 46 + # unbounded; a declared value caps it at that many seconds. 47 + timeout: int | None = None 48 + # How the broker runs this capability (PLAN.md §6.5): 49 + # "command" — run in the jail, capture output, return one result (default). 50 + # "background" — launch detached, return a handle; track in the registry. 51 + # "control" — operate on the background registry (see `control`); no jail. 52 + kind: str = "command" 53 + # For kind == "control": which registry operation this tool performs, 54 + # one of "status" | "output" | "stop". None for every other kind. 55 + control: str | None = None 56 + 57 + 58 + Sampling = dict[str, int | float] 59 + 60 + 61 + @dataclass(frozen=True) 62 + class ModelConfig: 63 + """The model an agent declares: backend binding + inference knobs (PLAN.md §9). 64 + 65 + A model id is only meaningful next to the base_url that serves it, so they 66 + travel together alongside provider-portable inference knobs. Secrets and 67 + deployment-specific headers are sourced from the environment, never from the 68 + Nix store. 69 + """ 70 + 71 + base_url: str | None = None 72 + name: str | None = None 73 + provider: str | None = None # provider type, e.g. "openai-compat" 74 + max_tokens: int | None = None 75 + sampling: Sampling | None = None 76 + 77 + 78 + @dataclass(frozen=True) 79 + class Manifest: 80 + tools: list[dict] # provider-neutral tool defs (name/description/parameters) 81 + capabilities: dict[str, Capability] 82 + # The agent's persona, declared in Nix. None when the agent declares none. 83 + system_prompt: str | None = None 84 + # The agent's model block, declared in Nix. None when the agent declares none, 85 + # in which case the harness defaults (or env overrides) supply everything. 86 + model: ModelConfig | None = None 87 + # The CA bundle path emitted by the agent flake. Exported into jailed tools 88 + # (base_env), and the Nix shell closure binds its store root. 89 + ca_bundle_file: str = "" 90 + # The baked baseline PATH for the jail (colon-joined `…/bin` store dirs), 91 + # emitted by Nix from the declared shell packages. Replaces a `nix develop` 92 + # resolution, so PATH always equals the bound shell closure. 93 + shell_path: str = "" 94 + # The baseline closure every jailed call binds (shell PATH packages + the CA 95 + # bundle), as the `store-paths` file path emitted by Nix and its realized 96 + # contents. `shell_closure` is filled after realization (manifest_loader). 97 + shell_closure_file: str = "" 98 + shell_closure: list[str] = field(default_factory=list) 99 + 100 + 101 + def _params_to_json_schema(params: dict[str, Param]) -> dict[str, Any]: 102 + """Project typed params into a provider-neutral JSON Schema object.""" 103 + properties: dict[str, Any] = {} 104 + required: list[str] = [] 105 + for name, param in params.items(): 106 + schema: dict[str, Any] = {"type": param.type, "description": param.description} 107 + if param.enum is not None: 108 + schema["enum"] = param.enum 109 + properties[name] = schema 110 + if param.required: 111 + required.append(name) 112 + return {"type": "object", "properties": properties, "required": required} 113 + 114 + 115 + def tool_from_capability(capability: Capability) -> dict: 116 + """The model-facing tool projection of a capability.""" 117 + return { 118 + "name": capability.name, 119 + "description": capability.description, 120 + "parameters": _params_to_json_schema(capability.params), 121 + } 122 + 123 + 124 + def build_manifest(capabilities: dict[str, Capability]) -> Manifest: 125 + """Build a Manifest, exposing every non-deny capability as a tool.""" 126 + tools = [ 127 + tool_from_capability(capability) 128 + for capability in capabilities.values() 129 + if capability.policy != "deny" 130 + ] 131 + return Manifest(tools=tools, capabilities=capabilities)
+22
tests/manifest_fixtures.py
··· 1 + from tartarus.manifest import Capability, Grant, Manifest, Param, build_manifest 2 + 3 + 4 + def echo_manifest() -> Manifest: 5 + capabilities = { 6 + "echo": Capability( 7 + name="echo", 8 + description="Echo a message back to the caller verbatim.", 9 + policy="auto", 10 + params={ 11 + "message": Param( 12 + type="string", 13 + description="The text to echo back.", 14 + required=True, 15 + ) 16 + }, 17 + grants=Grant(), 18 + runner="echo {message}", 19 + ) 20 + } 21 + 22 + return build_manifest(capabilities)
+52
tests/test_manifest.py
··· 1 + from tartarus.manifest import ( 2 + Capability, 3 + Grant, 4 + Param, 5 + build_manifest, 6 + tool_from_capability, 7 + ) 8 + from tests.manifest_fixtures import echo_manifest 9 + 10 + 11 + def test_echo_fixture_manifest_exposes_echo_tool(): 12 + manifest = echo_manifest() 13 + 14 + assert [tool["name"] for tool in manifest.tools] == ["echo"] 15 + assert "echo" in manifest.capabilities 16 + assert manifest.capabilities["echo"].policy == "auto" 17 + 18 + 19 + def test_tool_projection_builds_json_schema(): 20 + capability = Capability( 21 + name="demo", 22 + description="A demo capability.", 23 + policy="auto", 24 + params={ 25 + "direction": Param( 26 + "string", "Which way.", required=True, enum=["up", "down"] 27 + ), 28 + "steps": Param("integer", "How many."), 29 + }, 30 + grants=Grant(), 31 + runner="demo {direction}", 32 + ) 33 + 34 + tool = tool_from_capability(capability) 35 + 36 + assert tool["name"] == "demo" 37 + assert tool["parameters"]["required"] == ["direction"] 38 + assert tool["parameters"]["properties"]["direction"]["enum"] == ["up", "down"] 39 + assert tool["parameters"]["properties"]["steps"]["type"] == "integer" 40 + 41 + 42 + def test_deny_capabilities_are_not_projected_into_tools(): 43 + capabilities = { 44 + "open": Capability("open", "ok", "auto", {}, Grant(), "true"), 45 + "locked": Capability("locked", "no", "deny", {}, Grant(), "true"), 46 + } 47 + 48 + manifest = build_manifest(capabilities) 49 + 50 + tool_names = [tool["name"] for tool in manifest.tools] 51 + assert tool_names == ["open"] 52 + assert "locked" in manifest.capabilities
+206
uv.lock
··· 1 + version = 1 2 + revision = 3 3 + requires-python = ">=3.13" 4 + 5 + [[package]] 6 + name = "tartarus-nix" 7 + version = "0.1.0" 8 + source = { virtual = "." } 9 + dependencies = [ 10 + { name = "httpx" }, 11 + ] 12 + 13 + [package.dev-dependencies] 14 + dev = [ 15 + { name = "pytest" }, 16 + { name = "ruff" }, 17 + { name = "ty" }, 18 + ] 19 + 20 + [package.metadata] 21 + requires-dist = [{ name = "httpx", specifier = ">=0.28.1" }] 22 + 23 + [package.metadata.requires-dev] 24 + dev = [ 25 + { name = "pytest", specifier = ">=9.1.1" }, 26 + { name = "ruff", specifier = ">=0.15.20" }, 27 + { name = "ty", specifier = ">=0.0.55" }, 28 + ] 29 + 30 + [[package]] 31 + name = "anyio" 32 + version = "4.14.1" 33 + source = { registry = "https://pypi.org/simple" } 34 + dependencies = [ 35 + { name = "idna" }, 36 + ] 37 + sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } 38 + wheels = [ 39 + { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, 40 + ] 41 + 42 + [[package]] 43 + name = "certifi" 44 + version = "2026.6.17" 45 + source = { registry = "https://pypi.org/simple" } 46 + sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } 47 + wheels = [ 48 + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, 49 + ] 50 + 51 + [[package]] 52 + name = "colorama" 53 + version = "0.4.6" 54 + source = { registry = "https://pypi.org/simple" } 55 + sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 56 + wheels = [ 57 + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 58 + ] 59 + 60 + [[package]] 61 + name = "h11" 62 + version = "0.16.0" 63 + source = { registry = "https://pypi.org/simple" } 64 + sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } 65 + wheels = [ 66 + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, 67 + ] 68 + 69 + [[package]] 70 + name = "httpcore" 71 + version = "1.0.9" 72 + source = { registry = "https://pypi.org/simple" } 73 + dependencies = [ 74 + { name = "certifi" }, 75 + { name = "h11" }, 76 + ] 77 + sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } 78 + wheels = [ 79 + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, 80 + ] 81 + 82 + [[package]] 83 + name = "httpx" 84 + version = "0.28.1" 85 + source = { registry = "https://pypi.org/simple" } 86 + dependencies = [ 87 + { name = "anyio" }, 88 + { name = "certifi" }, 89 + { name = "httpcore" }, 90 + { name = "idna" }, 91 + ] 92 + sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } 93 + wheels = [ 94 + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, 95 + ] 96 + 97 + [[package]] 98 + name = "idna" 99 + version = "3.18" 100 + source = { registry = "https://pypi.org/simple" } 101 + sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } 102 + wheels = [ 103 + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, 104 + ] 105 + 106 + [[package]] 107 + name = "iniconfig" 108 + version = "2.3.0" 109 + source = { registry = "https://pypi.org/simple" } 110 + sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } 111 + wheels = [ 112 + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, 113 + ] 114 + 115 + [[package]] 116 + name = "packaging" 117 + version = "26.2" 118 + source = { registry = "https://pypi.org/simple" } 119 + sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } 120 + wheels = [ 121 + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, 122 + ] 123 + 124 + [[package]] 125 + name = "pluggy" 126 + version = "1.6.0" 127 + source = { registry = "https://pypi.org/simple" } 128 + sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } 129 + wheels = [ 130 + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, 131 + ] 132 + 133 + [[package]] 134 + name = "pygments" 135 + version = "2.20.0" 136 + source = { registry = "https://pypi.org/simple" } 137 + sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } 138 + wheels = [ 139 + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, 140 + ] 141 + 142 + [[package]] 143 + name = "pytest" 144 + version = "9.1.1" 145 + source = { registry = "https://pypi.org/simple" } 146 + dependencies = [ 147 + { name = "colorama", marker = "sys_platform == 'win32'" }, 148 + { name = "iniconfig" }, 149 + { name = "packaging" }, 150 + { name = "pluggy" }, 151 + { name = "pygments" }, 152 + ] 153 + sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } 154 + wheels = [ 155 + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, 156 + ] 157 + 158 + [[package]] 159 + name = "ruff" 160 + version = "0.15.20" 161 + source = { registry = "https://pypi.org/simple" } 162 + sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } 163 + wheels = [ 164 + { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, 165 + { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, 166 + { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, 167 + { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, 168 + { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, 169 + { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, 170 + { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, 171 + { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, 172 + { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, 173 + { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, 174 + { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, 175 + { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, 176 + { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, 177 + { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, 178 + { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, 179 + { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, 180 + { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, 181 + ] 182 + 183 + [[package]] 184 + name = "ty" 185 + version = "0.0.55" 186 + source = { registry = "https://pypi.org/simple" } 187 + sdist = { url = "https://files.pythonhosted.org/packages/08/48/f687c8d268e3581f2f104d1f2ac5944d5b5e841b3695c613b3f263e5bbf7/ty-0.0.55.tar.gz", hash = "sha256:88ca87073825a79a8327c550efcc86cec94344890244c5946f84c9e44a969f31", size = 6040230, upload-time = "2026-06-27T00:27:29.385Z" } 188 + wheels = [ 189 + { url = "https://files.pythonhosted.org/packages/87/a3/1a90ba7e5a61c6d09adb92346ddba97668095fc257b577af433e5ac4f404/ty-0.0.55-py3-none-linux_armv6l.whl", hash = "sha256:31e83eef512d066542fe990fe1a3b814423abd1616376c54e48af7045b3e1749", size = 11677249, upload-time = "2026-06-27T00:26:52.18Z" }, 190 + { url = "https://files.pythonhosted.org/packages/82/3a/669f9aa478c38243e213a2684db1502086026cfadc15bb1b29b7cbde030d/ty-0.0.55-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab4bca857950608fea73e269e2da369d43e6467131de85160d68e2fa466fa248", size = 11444180, upload-time = "2026-06-27T00:26:54.576Z" }, 191 + { url = "https://files.pythonhosted.org/packages/15/a4/6a4b2507a53ce6530c66c5b4fe0d58551eb1748ffa9e0696c32fdd55bbd4/ty-0.0.55-py3-none-macosx_11_0_arm64.whl", hash = "sha256:55032bfd31bf2c5355ee81bdc6407b144a1cc7ee41e5681dd1368e4cef2ba327", size = 10963134, upload-time = "2026-06-27T00:26:57.348Z" }, 192 + { url = "https://files.pythonhosted.org/packages/ce/ae/a3b1a0f1cc83b7d258662cb98aa80a720c2e671d0e8fa0d17a4d5d057a7a/ty-0.0.55-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1e049f69ce65b3c269af67624607f435e1c32319786c1e453ef9611502f295", size = 11493517, upload-time = "2026-06-27T00:26:59.26Z" }, 193 + { url = "https://files.pythonhosted.org/packages/0d/9f/311ce39065a979ef40a9b847f685c8e02464e53adf1671e081eea90640ca/ty-0.0.55-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:631409975c681d5a280fc5a99b7b32e9e801f33be7567c6b42ec331362f59d7d", size = 11460590, upload-time = "2026-06-27T00:27:01.425Z" }, 194 + { url = "https://files.pythonhosted.org/packages/cd/8f/3bf29aa77bd78aae48275153135a2052fa7d3ccdf1ecabeb99c8773abd66/ty-0.0.55-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e08cb0436e68b9351555ae8f2697138c9009b4d5b4ae4272232988b2a431a98f", size = 12098430, upload-time = "2026-06-27T00:27:03.596Z" }, 195 + { url = "https://files.pythonhosted.org/packages/bc/6e/e88411a88240b94640bba06fb6d0d92b247fbeef47ee2bc71f39e58c2558/ty-0.0.55-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16c215ad9f823829409b94ee188cfaa4563f6e1384f6ce3fecb1db75f6c7cf7c", size = 12673086, upload-time = "2026-06-27T00:27:05.589Z" }, 196 + { url = "https://files.pythonhosted.org/packages/6c/7e/8f1762fb7f9245a68ba5ae338d73c59403ce57554e5d311b8bb55027b0ec/ty-0.0.55-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b510eb8f4032baf11b7aee2f1d53babc3b4ca03939b9cdcf6a9d15761d575188", size = 12242559, upload-time = "2026-06-27T00:27:07.714Z" }, 197 + { url = "https://files.pythonhosted.org/packages/72/1f/143657daf2670d977dac83435f1fe03d4843efb798d8e1e75950e541aadd/ty-0.0.55-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ddc05e7959709c3b9b83aa627128a80446865e3c1a4882638dcff6d776dc34a", size = 12021409, upload-time = "2026-06-27T00:27:09.881Z" }, 198 + { url = "https://files.pythonhosted.org/packages/6d/30/69487c439dd1fad3a4a3d96f0a472193de297eaba6fc4b8ea687ce434ac2/ty-0.0.55-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:636e8e5078787b8c6916c94e1406719f10189a4ca6b37b813a5922ce5857a8c7", size = 12303807, upload-time = "2026-06-27T00:27:11.986Z" }, 199 + { url = "https://files.pythonhosted.org/packages/e8/ca/cd88b6493dafc7db077f5e17c0438eb3af6e2d6d08f616dbb52a8ddfd567/ty-0.0.55-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ef7d6deaacb73fec603666b5471f1dc5a5699aa84e11a6d4d644dd07ca72121e", size = 11441263, upload-time = "2026-06-27T00:27:14.087Z" }, 200 + { url = "https://files.pythonhosted.org/packages/aa/fe/66b6915671653ab739f71e4f1b0528e69da64429b7ebf3840c625b6e43f2/ty-0.0.55-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9aeea0fe5875d3cf37faf0e44d0fdf9669335467749741b8fc0103916fb5cd32", size = 11484584, upload-time = "2026-06-27T00:27:16.311Z" }, 201 + { url = "https://files.pythonhosted.org/packages/4a/4f/7a9c0bbac8b899e9f6c0ec110c6612f52e4db35f6bb17ddc0ef60384fa3e/ty-0.0.55-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0b699c01310dbd2705a07c97c5f4aaeedef61bd9adeea2e7c46aed32401d3576", size = 11759309, upload-time = "2026-06-27T00:27:18.471Z" }, 202 + { url = "https://files.pythonhosted.org/packages/ca/de/b6f8b1b69aa631b5716ef3f985c3b56de0e46c2499cc00d30c402b41f714/ty-0.0.55-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:32cbeba543e46de2a983ec6d525d8b56514f7422bd1e1b57c44ccf7bfa72c38a", size = 12128755, upload-time = "2026-06-27T00:27:20.55Z" }, 203 + { url = "https://files.pythonhosted.org/packages/7d/90/a912531e51ee7e076b42972479290fa687c0f5e747b7e773f3033164acaa/ty-0.0.55-py3-none-win32.whl", hash = "sha256:52b968e24eb4f7a5c3bd251db1f99f60dd385890356d38fc619d84f1b423446a", size = 11117501, upload-time = "2026-06-27T00:27:22.714Z" }, 204 + { url = "https://files.pythonhosted.org/packages/4c/7a/99d59843bf8908a7f9f4d13fda107dbad07b7faa28ecd7860eacf363fb1c/ty-0.0.55-py3-none-win_amd64.whl", hash = "sha256:bf39cbfdc0add44d94bd3fff1f53c351418d134b6a66b87efdb7876d7b7a2224", size = 12150106, upload-time = "2026-06-27T00:27:24.881Z" }, 205 + { url = "https://files.pythonhosted.org/packages/b3/44/20987505cedf2a865b08482f0eabc181fd9599b062964057ec8a128a4296/ty-0.0.55-py3-none-win_arm64.whl", hash = "sha256:f7f3700a9a060e8f1af11e4fb63fafcaf272b041781f4ccdfda2b3b5c6c1e439", size = 11560157, upload-time = "2026-06-27T00:27:27.332Z" }, 206 + ]