From 3a2b4c30cab54c59854239614e43ab7743a4d2b8 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 22:42:53 -0400 Subject: [PATCH 01/17] Limit native watcher failure delivery to its read and decode contract --- src/agent_comms/watched.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agent_comms/watched.py b/src/agent_comms/watched.py index 3a60f9cc02..11ccf42595 100644 --- a/src/agent_comms/watched.py +++ b/src/agent_comms/watched.py @@ -256,7 +256,7 @@ def wake(self) -> None: def _on_ready(self) -> None: try: self._drain() - except Exception as error: + except (OSError, ValueError) as error: # Callback failures must reach the waiting reader, not just stderr. self.loop.remove_reader(self.fd) self._publish(SourceFailed(error)) From 6ec31d92816fd28cd05e3e1ee811b876d11ce2d4 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 22:48:13 -0400 Subject: [PATCH 02/17] Record backend capability ownership and complete source boundary --- plans/REMAINING-SLOP-PLAN.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plans/REMAINING-SLOP-PLAN.md b/plans/REMAINING-SLOP-PLAN.md index 573f992bc3..94ead17d92 100644 --- a/plans/REMAINING-SLOP-PLAN.md +++ b/plans/REMAINING-SLOP-PLAN.md @@ -133,6 +133,22 @@ Three audits, one per catalog rung group, each finding read in the code and chec - `RecoveryCondition` matching a lifecycle by `declared_name` string equality across two families (`recovery_states.py:21-26`): `issubclass`. **6.4 Rosters and registries beside families (MEMB-1/2/4, about 120).** + +Backend capability slice (integration base `acb2ddd0e`, 2026-10-23): +`THREAD_CAPABILITIES`, the two serving flags and `validate_backends` independently +state which backend may serve threads. `ThreadServingBackend` will inherit +`Steers` and `Compacts`; `NewThreadBackend` will own new-thread selection from +the declared family. Capability bases must precede `AgentBackend`, whose +concrete refusal method would otherwise hide the abstract turn obligation. +Location attachment moves to sealing the concrete declarations, retaining one +backend per location and avoiding mutation by an incomplete declaration. +NRA census covers 1,528 Core/hooks/tools/tests and paired Toad modules: +6,973 original classes, 6,752 projected classes, zero parse omissions; the +221 unprojected originals remain visible. Source evidence is not a behavior +proof. The independent Pi producer/converter event comparison is retained; +deriving expected producer events from the converters would erase its check. +This slice does not close `RostersPhase` or edit its evidence declaration. + - Five hand-rolled `__init_subclass__` registries: `Carry.declared` (`installer/carry.py:45-54`), `ThreadRename.kinds` (`thread_management.py:557-567`, gone with 4.1), `BirthlessIncarnation.members` (`thread_identity.py:185-192`), `CliToolArguments.types` (`tool_calls.py:89-95`), `_start_phases`/`_end_events` (`pi_events.py:59-93`): `DeclaredFamily` or attributes through the MRO. - `PI_0_85_1_EVENT_KINDS`, 36 names re-typed beside `NativePiEvent.known_kinds()` (`pi_native_backend.py:63-81`, check at `agent_backend.py:428`): derive the union; the equality check goes. - `THREAD_CAPABILITIES` plus `serves_threads`/`serves_new_threads` flags plus `validate_backends` probing with `getattr` (`agent_backend.py:358-466`): `ThreadServingBackend(AgentBackend, Steers, Compacts)` and `NewThreadBackend`; ABC instantiation enforces the roles. From 29d106eea8e5acf0cfadb9ab169de5fb62b494ab Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 22:52:37 -0400 Subject: [PATCH 03/17] Exercise native descriptor failure delivery through the real watcher --- tests/test_watched.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_watched.py b/tests/test_watched.py index f4efc575bc..551fe14c57 100644 --- a/tests/test_watched.py +++ b/tests/test_watched.py @@ -1,6 +1,7 @@ """The declared bus watch uses real native commit and replacement notifications.""" import asyncio +import errno from contextlib import aclosing from concurrent.futures import Future from dataclasses import replace @@ -173,6 +174,20 @@ async def test_native_file_removal_wakes_without_unrelated_events(tmp_path, remo await asyncio.wait_for(changes.wait(), 1) +async def test_failed_native_descriptor_reaches_waiting_reader_and_survives_later_wake(tmp_path): + path = tmp_path / "observed" + async with ChangeSignal.opened(FileSource(path)) as changes: + changes.prepare() + # Retire the real native descriptor before a queued readiness callback runs. + # ExitStack close is idempotent; context retirement will not close it twice. + changes.close() + changes._on_ready() + changes.wake() + with pytest.raises(OSError) as failed: + await asyncio.wait_for(changes.wait(), 1) + assert failed.value.errno == errno.EBADF + + async def test_native_file_burst_wakes_before_the_producer_finishes(tmp_path): path = tmp_path / "stream" path.write_text("0") From d8559df817e72769fcd47bcd081efde435d8a884 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 22:59:47 -0400 Subject: [PATCH 04/17] Inherit thread backend contracts and derive default selection --- plans/REMAINING-SLOP-PLAN.md | 33 +++++--- src/agent_comms/agent_backend.py | 94 +++++++++------------- src/agent_comms/pi_durable_backend.py | 3 - src/agent_comms/pi_native_backend.py | 9 +-- src/agent_comms/thread_identity.py | 4 +- src/agent_comms/thread_text.py | 3 +- src/agent_comms/threads.py | 4 +- tests/test_agent_backend.py | 86 ++++++++------------ tests/test_agent_backend_deletion_gates.py | 2 +- tests/test_durable_runtime_location.py | 4 +- tests/test_package_imports.py | 4 +- 11 files changed, 109 insertions(+), 137 deletions(-) diff --git a/plans/REMAINING-SLOP-PLAN.md b/plans/REMAINING-SLOP-PLAN.md index 94ead17d92..c5c5009f0b 100644 --- a/plans/REMAINING-SLOP-PLAN.md +++ b/plans/REMAINING-SLOP-PLAN.md @@ -134,24 +134,37 @@ Three audits, one per catalog rung group, each finding read in the code and chec **6.4 Rosters and registries beside families (MEMB-1/2/4, about 120).** -Backend capability slice (integration base `acb2ddd0e`, 2026-10-23): -`THREAD_CAPABILITIES`, the two serving flags and `validate_backends` independently -state which backend may serve threads. `ThreadServingBackend` will inherit -`Steers` and `Compacts`; `NewThreadBackend` will own new-thread selection from -the declared family. Capability bases must precede `AgentBackend`, whose -concrete refusal method would otherwise hide the abstract turn obligation. -Location attachment moves to sealing the concrete declarations, retaining one -backend per location and avoiding mutation by an incomplete declaration. +Backend capability slice (#2623, integration base `acb2ddd0e`, 2026-10-23): +deleted `THREAD_CAPABILITIES`, both serving flags, `validate_backends` and the +old default-selection method. `ThreadServingBackend` inherits `Steers` and +`Compacts`; both record default factories invoke `NewThreadBackend.selected`. +Capability bases precede `AgentBackend`, whose concrete refusal method would +otherwise hide the abstract turn obligation. Sealing attaches only concrete +locations after validating unique ownership. An incomplete declaration cannot +rebind a location. `NoSessionFile` constructs the shared `UnstartedRuntime`; +both copied backend `unstarted_type` declarations are deleted. NRA census covers 1,528 Core/hooks/tools/tests and paired Toad modules: 6,973 original classes, 6,752 projected classes, zero parse omissions; the 221 unprojected originals remain visible. Source evidence is not a behavior -proof. The independent Pi producer/converter event comparison is retained; +proof. Afterward: 1,529 modules, 6,974 original/6,753 projected classes, zero +parse omissions; Core remains 953 modules/5,341 original classes. The extra +dependency module/class came from the concurrently edited Toad source tree. +The before/after receipts remain in the owned backend-capability-rosters +scratch directory. Focused source checks pass 27 cases (25.69s), including +fresh CLI/UI imports, typed native/durable locations, ABC refusal without +location mutation, worker backend reuse and a real durable host reading 260 +saved rows while its SQLite writer stays open. The initial 24-pass/3-failure +run exposed an untyped session-file fixture, a stale caller roster and missing +local Node dependencies; all three were corrected, without counting that run +as green. Installed copied-agent DM/reply/tab-return qualification belongs to +the later paired integration; this branch has not changed the live runtime. +The independent Pi producer/converter event comparison is retained; deriving expected producer events from the converters would erase its check. This slice does not close `RostersPhase` or edit its evidence declaration. - Five hand-rolled `__init_subclass__` registries: `Carry.declared` (`installer/carry.py:45-54`), `ThreadRename.kinds` (`thread_management.py:557-567`, gone with 4.1), `BirthlessIncarnation.members` (`thread_identity.py:185-192`), `CliToolArguments.types` (`tool_calls.py:89-95`), `_start_phases`/`_end_events` (`pi_events.py:59-93`): `DeclaredFamily` or attributes through the MRO. - `PI_0_85_1_EVENT_KINDS`, 36 names re-typed beside `NativePiEvent.known_kinds()` (`pi_native_backend.py:63-81`, check at `agent_backend.py:428`): derive the union; the equality check goes. -- `THREAD_CAPABILITIES` plus `serves_threads`/`serves_new_threads` flags plus `validate_backends` probing with `getattr` (`agent_backend.py:358-466`): `ThreadServingBackend(AgentBackend, Steers, Compacts)` and `NewThreadBackend`; ABC instantiation enforces the roles. +- Backend roles: removed by #2623 through `ThreadServingBackend(Steers, Compacts, AgentBackend)` and declaration-derived `NewThreadBackend.selected`; abstract methods enforce thread obligations. `PiDurableBackend` remains a non-thread backend. - Display names re-typed from class names in about 40 places (`tools.py` labels, `notification_progress.py:45-157`, `todos.py:1502/2012/2088/2605/2675`, `display_order.py:76-136`, `relationships.py:382/397`, `turn_phase.py:169/218`, `config_options.py:192-194`, `input_attempt.py:400/440/447` `public_status`): derived from the declared name, literal only where the text differs. - Sigils `#`/`@` spelled by hand at about 22 sites (`catalog_document.py:61-232`, `channels.py`, `channel_targets.py`, `goal_presentation.py:71-72`, `goal_actions.py:305/318`, …): owned by `Tag.channel` and `ThreadTarget.mention`. - Store and file names restated outside their class: `root / "wire"` in about 20 places across 17 modules (the virtual bus plan's done-grep for `_wire_lock_path` misses every `_store_lock(root / "wire")`; its grep gains that pattern), `~/.local/state/agent-comms` six times, bridge inbox dirs, `"registry.json"` at `installer/upgrade.py:566`, `".registry-owner-guard"` at `goal_history.py:208`, `active-route.json` five times, Pi `auth.json` in four modules: `X.path_in(root)` and one state-home owner. diff --git a/src/agent_comms/agent_backend.py b/src/agent_comms/agent_backend.py index 7d1a20c6ea..743f8a25de 100644 --- a/src/agent_comms/agent_backend.py +++ b/src/agent_comms/agent_backend.py @@ -6,10 +6,10 @@ ever sees the records declared here. A backend converts its runtime's own types to these records; no consumer outside a backend reads runtime types. -Capabilities are mixin roles composed through the MRO. A backend that serves -threads must implement every role in ``THREAD_CAPABILITIES``; the abstract -methods enforce the signatures, and :meth:`AgentBackend.seal` checks the rest -once every backend module is declared, then freezes the registry. +Capabilities are mixin roles composed through the MRO. Thread-serving backends +inherit ``ThreadServingBackend``; new-thread selection uses ``NewThreadBackend``. +Their inherited abstract methods enforce the required behavior. Sealing binds +each concrete backend to its location and freezes the registry. This module is deliberately light: it is imported by ``threads`` and must not import any runtime implementation at module level. @@ -19,7 +19,7 @@ import asyncio from abc import ABC, abstractmethod -from collections.abc import AsyncIterator, Awaitable, Callable, Iterable +from collections.abc import AsyncIterator, Awaitable, Callable from dataclasses import dataclass from enum import Enum from types import MappingProxyType @@ -355,12 +355,6 @@ async def fork(cls, parent: Thread, launcher: str, runtime: LaunchRuntime) -> Ru """ -THREAD_CAPABILITIES: tuple[type, ...] = ( - Steers, - Compacts, -) - - # --------------------------------------------------------------------------- # The family. # --------------------------------------------------------------------------- @@ -369,15 +363,12 @@ async def fork(cls, parent: Thread, launcher: str, runtime: LaunchRuntime) -> Ru class AgentBackend(DeclaredFamily, affix="Backend"): """One agent runtime, keyed by ``declared_name`` ("pi_native", ...). - A backend declares its location type, whether it serves threads, whether - new threads get it, and the event kinds its runtime protocol emits. One + A backend declares its location type, capabilities through inheritance, + and the event kinds its runtime protocol emits. One instance holds one thread session's runtime process. """ location_type: ClassVar[type[RuntimeLocation]] - unstarted_type: ClassVar[type[RuntimeLocation]] - serves_threads: ClassVar[bool] = True - serves_new_threads: ClassVar[bool] = False event_kinds: ClassVar[frozenset[str]] _sealed: ClassVar[bool] = False @@ -391,7 +382,6 @@ def __init_subclass__(cls, **options: Any) -> None: "before the Thread backend lookup seals its declarations." ) super().__init_subclass__(**options) - cls.location_type._backend = cls @classmethod @abstractmethod @@ -402,16 +392,22 @@ def for_worker(cls, runner: Any, session_id: str) -> Self: @abstractmethod def new_location(cls, root, worktree) -> RuntimeLocation: ... - @classmethod - def for_new_threads(cls) -> type[AgentBackend]: - """The backend a newly declared thread gets.""" - (member,) = (m for m in AgentBackend.members_with(AgentBackend) if m.serves_new_threads) - return member - @classmethod def seal(cls) -> None: - """Validate every declared backend and freeze the registry.""" - validate_backends(dict(AgentBackend.__registry__).values()) + """Bind concrete locations after validating all declaration relationships.""" + NewThreadBackend.selected() + owners: dict[type[RuntimeLocation], type[AgentBackend]] = {} + for member in cls.members_with(cls): + location = member.location_type + if not issubclass(location, RuntimeLocation): + raise BackendDeclarationError(f"{member.__name__} declares no RuntimeLocation type") + if previous := owners.get(location): + raise BackendDeclarationError( + f"location type {location.__name__} belongs to several backends " + f"{previous.__name__}, {member.__name__}") + owners[location] = member + for location, owner in owners.items(): + location._backend = owner AgentBackend.__registry__ = MappingProxyType(dict(AgentBackend.__registry__)) # type: ignore[assignment] AgentBackend._sealed = True @@ -437,33 +433,23 @@ def _event_family_problems(backend: type, family: type[DeclaredFamily]) -> list[ return problems -def validate_backends(members: Iterable[type]) -> None: - """Check backend declarations; raise once, naming every missing piece.""" - members = tuple(members) - problems: list[str] = [] - owners: dict[type, list[str]] = {} - for member in members: - name = member.__name__ - location = getattr(member, "location_type", None) - if not (isinstance(location, type) and issubclass(location, RuntimeLocation)): - problems.append(f"{name} declares no RuntimeLocation type") - else: - owners.setdefault(location, []).append(name) - if not isinstance(getattr(member, "event_kinds", None), frozenset): - problems.append(f"{name} declares no event_kinds") - if member.serves_threads: - missing = [role.__name__ for role in THREAD_CAPABILITIES if not issubclass(member, role)] - if missing: - problems.append(f"{name} serves threads but lacks roles {missing}") - unimplemented = sorted(getattr(member, "__abstractmethods__", ())) - if unimplemented: - problems.append(f"{name} leaves methods abstract {unimplemented}") - for location, names in owners.items(): - if len(names) > 1: - problems.append(f"location type {location.__name__} belongs to several backends {names}") - new_thread_backends = [member.__name__ for member in members if member.serves_new_threads] - if len(new_thread_backends) != 1: - problems.append(f"exactly one backend must serve new threads, found {new_thread_backends}") - if problems: - raise BackendDeclarationError("; ".join(problems)) +class ThreadServingBackend(Steers, Compacts, AgentBackend, declared_name=None): + """A thread backend implements steering, turn lifecycle and compaction together. + + The capability bases precede AgentBackend so its non-thread turn refusal + cannot satisfy the abstract turn contract. + """ + + +class NewThreadBackend(ThreadServingBackend, declared_name=None): + """The complete thread backend selected for newly registered threads.""" + + @classmethod + def selected(cls) -> type[Self]: + members = cls.members_with(cls) + if len(members) != 1: + raise BackendDeclarationError( + f"exactly one backend must serve new threads, found " + f"{[member.__name__ for member in members]}") + return members[0] diff --git a/src/agent_comms/pi_durable_backend.py b/src/agent_comms/pi_durable_backend.py index c492c0f089..e42d786097 100644 --- a/src/agent_comms/pi_durable_backend.py +++ b/src/agent_comms/pi_durable_backend.py @@ -37,7 +37,6 @@ from .pi_payloads import PiOmitted, PiResponseData from .pi_vocabulary import ThinkingLevelField from .transcript_sources import TranscriptSource -from .unstarted_runtime import UnstartedRuntime @dataclass(frozen=True) @@ -206,9 +205,7 @@ class CompactDurable(ExactResponse, Compact): class PiDurableBackend(AgentBackend, Compacts): - serves_threads = False location_type = DurableDatabase - unstarted_type = UnstartedRuntime event_kinds = frozenset(kind for member in DurablePiEvent.members_with(DurablePiEvent) for kind in member.known_kinds()) def __init__(self): diff --git a/src/agent_comms/pi_native_backend.py b/src/agent_comms/pi_native_backend.py index 724324f39f..40b8799e49 100644 --- a/src/agent_comms/pi_native_backend.py +++ b/src/agent_comms/pi_native_backend.py @@ -23,14 +23,13 @@ from agent_comms.pi_payloads import PiOmitted from .agent_backend import ( - AgentBackend, CompactionOutcome, - Compacts, FailedCompaction, Forks, InputContent, Interrupts, NothingToCompaction, + NewThreadBackend, PlacedCompaction, RuntimeLocation, TurnDelivery, @@ -42,7 +41,6 @@ from .native_session_reopen import NativeSessionIdentity from .native_transcript import NativeTranscript from .selected_session import SavedSelectedSession -from .unstarted_runtime import UnstartedRuntime if TYPE_CHECKING: from .agent_events import AgentEvent, AgentInfo @@ -179,9 +177,8 @@ async def close_idle(self) -> None: class PiNativeBackend( PersistentPiSession, Interrupts, - Compacts, Forks, - AgentBackend, + NewThreadBackend, ): """Pi 0.85.1 over RPC: one retained child per thread session in this worker. @@ -191,8 +188,6 @@ class PiNativeBackend( """ location_type: ClassVar[type[RuntimeLocation]] = PiSessionFile - unstarted_type: ClassVar[type[RuntimeLocation]] = UnstartedRuntime - serves_new_threads: ClassVar[bool] = True event_kinds: ClassVar[frozenset[str]] = PI_0_85_1_EVENT_KINDS def __init__( diff --git a/src/agent_comms/thread_identity.py b/src/agent_comms/thread_identity.py index 71af5ff476..f15551b46c 100644 --- a/src/agent_comms/thread_identity.py +++ b/src/agent_comms/thread_identity.py @@ -16,7 +16,7 @@ from agent_comms.participant_identity import ParticipantIdentity -from .agent_backend import AgentBackend, RuntimeLocation +from .agent_backend import AgentBackend, NewThreadBackend, RuntimeLocation from .channel_targets import Tag from .cli_options import JsonFlagRecord from .declared_family import DeclaredFamily, declared_words @@ -306,7 +306,7 @@ class ThreadProvenance: session_file: SessionFile = field(default_factory=NoSessionFile) title: ThreadText = field(default_factory=NoThreadText) backend: Annotated[type[AgentBackend], WireOmitDefault()] = field( - default_factory=AgentBackend.for_new_threads + default_factory=NewThreadBackend.selected ) @cached_property diff --git a/src/agent_comms/thread_text.py b/src/agent_comms/thread_text.py index 7a4467ec03..7b142c0f6f 100644 --- a/src/agent_comms/thread_text.py +++ b/src/agent_comms/thread_text.py @@ -157,7 +157,8 @@ def applied(self, previous: SessionFile) -> SessionFile: @dataclass(frozen=True, slots=True) class NoSessionFile(AbsentText, SessionFile): def location(self, backend: type[AgentBackend]) -> RuntimeLocation: - return backend.unstarted_type(backend) + from .unstarted_runtime import UnstartedRuntime + return UnstartedRuntime(backend) def applied(self, previous: SessionFile) -> SessionFile: return previous diff --git a/src/agent_comms/threads.py b/src/agent_comms/threads.py index 6e6820fab4..5ec6894b55 100644 --- a/src/agent_comms/threads.py +++ b/src/agent_comms/threads.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, ClassVar, Annotated -from .agent_backend import AgentBackend +from .agent_backend import AgentBackend, NewThreadBackend from .child_process import NoProcess, ProcessIdentity, ProcessState from .cli_result import ThreadToolCatalogResult from .context_segments.wake_values import WakeWorkContext @@ -202,7 +202,7 @@ class Thread(DurableFile, PatternFile, ThreadProvenance): type[AgentBackend], WireOmitDefault(), RegistrationPolicy(InheritPrevious), - ] = field(default_factory=AgentBackend.for_new_threads, kw_only=True) + ] = field(default_factory=NewThreadBackend.selected, kw_only=True) turn_record: TurnRecord = field(default_factory=NoTurnRecord) channel_scope_generation: int = 0 turn_generation: int = 0 diff --git a/tests/test_agent_backend.py b/tests/test_agent_backend.py index df576eef92..68c19a6436 100644 --- a/tests/test_agent_backend.py +++ b/tests/test_agent_backend.py @@ -18,6 +18,7 @@ from agent_comms.threads import Thread from agent_comms.unstarted_runtime import UnstartedRuntime from agent_comms.thread_model import DeclaredModel +from agent_comms.thread_text import NoSessionFile, SavedSessionFile # -- registry --------------------------------------------------------------- @@ -25,7 +26,7 @@ def test_registry_holds_the_pi_native_backend_and_is_frozen(): assert ab.AgentBackend.names() == ("pi_native", "pi_durable") assert ab.AgentBackend.member_named("pi_native") is PiNativeBackend - assert ab.AgentBackend.for_new_threads() is PiNativeBackend + assert ab.NewThreadBackend.selected() is PiNativeBackend assert isinstance(ab.AgentBackend.__registry__, MappingProxyType) assert ab.AgentBackend.member_named("pi_durable") is PiDurableBackend @@ -40,18 +41,21 @@ class LateBackend(ab.AgentBackend): # noqa: F841 def test_pi_native_serves_threads_with_every_thread_capability(): - assert PiNativeBackend.serves_threads - for role in (*ab.THREAD_CAPABILITIES, ab.Interrupts, ab.Forks): - assert issubclass(PiNativeBackend, role), role + assert issubclass(PiNativeBackend, ab.NewThreadBackend) + assert issubclass(PiNativeBackend, ab.ThreadServingBackend) + assert issubclass(PiNativeBackend, ab.Interrupts) + assert issubclass(PiNativeBackend, ab.Forks) + assert not issubclass(PiDurableBackend, ab.ThreadServingBackend) assert not PiNativeBackend.__abstractmethods__ assert PiNativeBackend.location_type is PiSessionFile -@pytest.mark.parametrize("location", [ - PiSessionFile("/home/ts/wt/test/session.jsonl"), UnstartedRuntime(PiNativeBackend), +@pytest.mark.parametrize("session_file, location", [ + (SavedSessionFile("/home/ts/wt/test/session.jsonl"), PiSessionFile("/home/ts/wt/test/session.jsonl")), + (NoSessionFile(), UnstartedRuntime(PiNativeBackend)), ]) -def test_runtime_location_round_trip_uses_declared_backend(location, *, thread_registration): - thread = thread_registration.new_thread('location', frozenset(), '/home/ts/wt/test', session_file=location.read_key) +def test_runtime_location_round_trip_uses_declared_backend(session_file, location, *, thread_registration): + thread = thread_registration.new_thread('location', frozenset(), '/home/ts/wt/test', session_file=session_file) restored = FieldCodec.decode(Thread, FieldCodec.encode(thread)) assert restored.runtime == location assert restored.runtime is restored.runtime @@ -63,57 +67,33 @@ def test_runtime_location_round_trip_uses_declared_backend(location, *, thread_r # -- declaration checks ------------------------------------------------------- -class _Location(ab.RuntimeLocation): - def location_payload(self): - return None +def test_incomplete_thread_backend_stays_abstract_and_cannot_rebind_a_location(monkeypatch): + original = PiSessionFile("saved-session").backend + with monkeypatch.context() as declarations: + declarations.setattr(ab.AgentBackend, "_sealed", False) + class IncompleteNative(PiNativeBackend, declared_name=None): + compact = ab.Compacts.compact -class _OtherLocation(ab.RuntimeLocation): - def location_payload(self): - return None + with pytest.raises(TypeError, match="compact"): + IncompleteNative() + assert IncompleteNative not in ab.AgentBackend.members_with(ab.AgentBackend) + assert PiSessionFile("saved-session").backend is original + with pytest.raises(TypeError, match="run_turn"): + ab.ThreadServingBackend() -def _declared(name, *roles, location=_Location, new_threads=False, serves=True, kinds=frozenset()): - return type(name, roles or (object,), { - "location_type": location, "serves_new_threads": new_threads, - "serves_threads": serves, "event_kinds": kinds, - }) +def test_sealing_rejects_competing_location_owners_before_attaching_any(monkeypatch): + monkeypatch.setattr(PiDurableBackend, "location_type", PiSessionFile) + with pytest.raises(ab.BackendDeclarationError, match="belongs to several backends"): + ab.AgentBackend.seal() + assert PiSessionFile("saved-session").backend is PiNativeBackend -def test_validation_names_missing_roles_and_abstract_methods(): - class HalfBackend(ab.Steers): - pass - - HalfBackend.location_type = _Location - HalfBackend.serves_threads = True - HalfBackend.serves_new_threads = True - HalfBackend.event_kinds = frozenset() - with pytest.raises(ab.BackendDeclarationError) as error: - ab.validate_backends([HalfBackend]) - message = str(error.value) - for role in ("Compacts",): - assert role in message - assert "leaves methods abstract" in message - for method in ("run_turn", "abort", "close_idle"): - assert repr(method) in message - - -def test_validation_requires_one_location_type_per_backend_and_one_default(): - first = _declared("FirstBackend", serves=False, new_threads=True) - second = _declared("SecondBackend", serves=False, new_threads=True) - with pytest.raises(ab.BackendDeclarationError) as error: - ab.validate_backends([first, second]) - assert "belongs to several backends ['FirstBackend', 'SecondBackend']" in str(error.value) - assert "exactly one backend must serve new threads" in str(error.value) - ab.validate_backends([first, _declared("Other", serves=False, location=_OtherLocation)]) - - -def test_validation_requires_a_location_and_event_kinds(): - bare = type("BareBackend", (), {"serves_threads": False, "serves_new_threads": True}) - with pytest.raises(ab.BackendDeclarationError) as error: - ab.validate_backends([bare]) - assert "BareBackend declares no RuntimeLocation type" in str(error.value) - assert "BareBackend declares no event_kinds" in str(error.value) +def test_new_thread_selection_requires_one_concrete_nominal_member(monkeypatch): + monkeypatch.setattr(ab.AgentBackend, "__registry__", {"pi_durable": PiDurableBackend}) + with pytest.raises(ab.BackendDeclarationError, match="exactly one backend"): + ab.NewThreadBackend.selected() class _ProbeEvent(DeclaredFamily): diff --git a/tests/test_agent_backend_deletion_gates.py b/tests/test_agent_backend_deletion_gates.py index ae5d95c496..d91ed9dd15 100644 --- a/tests/test_agent_backend_deletion_gates.py +++ b/tests/test_agent_backend_deletion_gates.py @@ -34,7 +34,7 @@ def _called_names(tree: ast.AST) -> set[str]: @pytest.mark.parametrize(("name", "owners"), [ # The Pi turn transport is the backend's private implementation. ("stream_agent_events", {"pi_native_backend.py"}), - ("terminate_task_process", {"backend.py", "native_session_prepare.py", "pi_native_backend.py"}), + ("terminate_task_process", {"pi_native_backend.py"}), # A session's Pi child is created only by its backend, or by a foreground # execution that owns its one child (default_factory, not a call). ("PersistentPiSession", set()), diff --git a/tests/test_durable_runtime_location.py b/tests/test_durable_runtime_location.py index 0f23675f46..d67fedd706 100644 --- a/tests/test_durable_runtime_location.py +++ b/tests/test_durable_runtime_location.py @@ -6,7 +6,7 @@ import pytest from agent_comms.thread_text import NoSessionFile, SavedSessionFile -from agent_comms.agent_backend import AgentBackend +from agent_comms.agent_backend import NewThreadBackend from agent_comms.field_codec import FieldCodec from agent_comms.turn_digests import LastTurns from agent_comms.pi_durable_backend import DurableDatabase, PiDurableBackend, Inspect @@ -23,7 +23,7 @@ def test_durable_thread_roundtrip_and_worker_selection(tmp_path, saved, *, threa restored = FieldCodec.decode(Thread, FieldCodec.encode(thread)) assert restored.runtime == (location if saved else UnstartedRuntime(PiDurableBackend)) assert restored.runtime.backend is PiDurableBackend - assert AgentBackend.for_new_threads() is PiNativeBackend + assert NewThreadBackend.selected() is PiNativeBackend assert not DurableDatabase.__abstractmethods__ class Runner: persistent_backends = {} diff --git a/tests/test_package_imports.py b/tests/test_package_imports.py index 754a825938..959c2bb044 100644 --- a/tests/test_package_imports.py +++ b/tests/test_package_imports.py @@ -15,13 +15,13 @@ def test_entrypoint_keeps_catalog_out_and_thread_lookup_seals_backends(entrypoin sys.path.insert(0, {str(source)!r}) importlib.import_module({entrypoint!r}) from agent_comms.threads import Thread -from agent_comms.agent_backend import AgentBackend +from agent_comms.agent_backend import AgentBackend, NewThreadBackend print(json.dumps({{ 'catalog_loaded': 'agent_comms.tools' in sys.modules, 'sealed': AgentBackend._sealed, 'native': AgentBackend.member_named('pi_native').declared_name, 'durable': AgentBackend.member_named('pi_durable').declared_name, - 'default': AgentBackend.for_new_threads().declared_name, + 'default': NewThreadBackend.selected().declared_name, }})) """ completed = subprocess.run( From 55edba7a486815d064b0d96436a3130a92adf015 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 23:10:45 -0400 Subject: [PATCH 05/17] Share typed one-field encoding with JSON shape families --- src/agent_comms/field_codec.py | 21 +++++++++++---------- tests/test_json_shape_field_codec.py | 23 +++++++++++++++++++++-- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/agent_comms/field_codec.py b/src/agent_comms/field_codec.py index 1876365825..466b9b8c80 100644 --- a/src/agent_comms/field_codec.py +++ b/src/agent_comms/field_codec.py @@ -873,14 +873,22 @@ def decode_value(cls, data: object, structural: ValueDecoder[object]) -> object: def from_wire(cls, data: object) -> object: ... -class SingleFieldValue(ExternalJsonShape, WireValue): - """One declared field is the whole wire value, not an object envelope.""" +class SingleValueEncoding(WireValue): + """Encode one declared field as the whole wire value.""" @classmethod def wire_field(cls): entry, = FieldCodec._record_plan(cls).fields return entry + def to_wire(self): + entry = self.wire_field() + return FieldCodec.encode_document(getattr(self, entry.name), entry.annotation) + + +class SingleFieldValue(ExternalJsonShape, SingleValueEncoding): + """One declared field is the whole wire value, not an object envelope.""" + @classmethod def from_wire(cls, data): entry = cls.wire_field() @@ -894,10 +902,6 @@ def schema(cls): def argument_options(cls) -> dict[str, object]: return compile_decoder(cls.wire_field().annotation).argument_options() - def to_wire(self): - entry = self.wire_field() - return FieldCodec.encode_document(getattr(self, entry.name), entry.annotation) - class SubclassUnion(DeclaredFamily, AcceptsNull, ExternalJsonShape, WireValue): """An untagged wire family; each union declares its own root with affix="".""" @@ -983,7 +987,7 @@ def from_json_value(cls, value): return cls(value=value) -class JsonShapeFamily(AcceptsNull, ExternalJsonShape, WireValue): +class JsonShapeFamily(AcceptsNull, ExternalJsonShape, SingleValueEncoding): """Untagged external JSON enters one declaration-owned value family. Put DeclaredFamily before this capability in the root's bases, preserving @@ -1045,9 +1049,6 @@ def schema(cls) -> dict[str, Any]: for _, annotation in cls._json_owners.values()] return FieldCodec.alternatives_schema("anyOf", alternatives) - def to_wire(self): - return self.value - class TextRepresentation[V](FieldRepresentation): """External text scalars share strict decoding at the canonical boundary. diff --git a/tests/test_json_shape_field_codec.py b/tests/test_json_shape_field_codec.py index ba4163b6fb..6b491d2fde 100644 --- a/tests/test_json_shape_field_codec.py +++ b/tests/test_json_shape_field_codec.py @@ -2,11 +2,14 @@ from abc import abstractmethod from dataclasses import dataclass +import json import pytest from agent_comms.declared_family import DeclaredFamily -from agent_comms.field_codec import FieldCodec, JsonShapeFamily, JsonShapeMember, MalformedRecord +from agent_comms.field_codec import ( + FieldCodec, JsonShapeFamily, JsonShapeMember, MalformedRecord, SingleFieldValue, +) from agent_comms.launch_runtime import ImportPath, NoImportPath, SpecifiedImportPath from agent_comms.native_claim_epoch import NativeClaimEpoch, RecordedNativeClaim, UnrecordedNativeClaim from agent_comms.selected_triage import ( @@ -79,6 +82,8 @@ def test_external_shape_family_uses_one_codec_for_children_and_new_cases(): assert isinstance(value.value["details"].value[2], BooleanValue) assert isinstance(value.value["details"].value[3], NullValue) assert value.render() == (("failure", ("400",), "False", ""),) + assert value.to_wire() == raw + assert json.loads(json.dumps(value.to_wire())) == raw assert FieldCodec.encode(value) == raw assert FieldCodec.project(value, "status") == raw assert ExternalValue.member_named("text") is TextValue @@ -100,6 +105,19 @@ def render(self): FieldCodec.decode(ExternalValue, float("nan")) +def test_one_field_encoding_preserves_distinct_cli_argument_contracts(): + @dataclass(frozen=True) + class ScalarArgument(SingleFieldValue): + value: int + + assert ScalarArgument(5).to_wire() == 5 + assert ScalarArgument.from_wire(5) == ScalarArgument(5) + assert ScalarArgument.argument_options() == {"type": int} + assert ExternalValue.argument_options() == {} + assert ImportPath.argument_options() == {} + assert NativeClaimEpoch.argument_options() == {} + + def test_semantic_members_stay_out_of_shape_selection(): class Meaning(DeclaredFamily, JsonShapeFamily, affix="Meaning"): pass @@ -124,12 +142,13 @@ def from_json_value(cls, value): (NativeClaimEpoch, None, UnrecordedNativeClaim(), "5"), (NativeClaimEpoch, 5, RecordedNativeClaim(5), "5"), (TriageDecisionRecord, None, AbsentTriageDecisionRecord(), 1), - (TriageDecisionRecord, "IGNORE", DecidedTriageRecord(IgnoreSelectedTriage), 1), + (TriageDecisionRecord, "ignore", DecidedTriageRecord(IgnoreSelectedTriage), "IGNORE"), (ImportPath, None, NoImportPath(), 1), (ImportPath, "/opt/lib", SpecifiedImportPath("/opt/lib"), 1), ]) def test_shape_family_decodes_each_member_and_refuses_an_unowned_type(family, wire, expected, refused): assert FieldCodec.decode(family, wire) == expected + assert expected.to_wire() == wire with pytest.raises(MalformedRecord): FieldCodec.decode(family, refused) From 9215d556a44b584ec951ba066aa91a18a5ccf530 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 23:12:47 -0400 Subject: [PATCH 06/17] Remove remaining physical bus locks through existing state owners --- src/agent_comms/collaboration_ledger.py | 5 ++- .../coordination_tables/refusals.py | 5 ++- src/agent_comms/view_unread.py | 16 ++++----- tests/test_held_sends.py | 35 +++++++++++++++++++ 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/agent_comms/collaboration_ledger.py b/src/agent_comms/collaboration_ledger.py index 66c2dc4913..c5686169e0 100644 --- a/src/agent_comms/collaboration_ledger.py +++ b/src/agent_comms/collaboration_ledger.py @@ -6,7 +6,6 @@ from .errors import UnregisteredThreadError from .registration import Registration from .shared_ledger import SharedLedger -from .store_files import _store_lock @dataclass(frozen=True, slots=True) @@ -14,7 +13,7 @@ class CollaborationLedger(SharedLedger): registry: Registration def merge(self, updates: Mapping[str, object], author: str) -> None: - with _store_lock(self.path.with_name("wire")): - if author not in self.registry: + with self.registry.store.reading_snapshot() as registry: + if registry.canonical_name(author) not in registry.threads: raise UnregisteredThreadError(f"Author {author!r} is not registered.") super().merge(updates, author) diff --git a/src/agent_comms/coordination_tables/refusals.py b/src/agent_comms/coordination_tables/refusals.py index 8298ad08c4..2324a781c8 100644 --- a/src/agent_comms/coordination_tables/refusals.py +++ b/src/agent_comms/coordination_tables/refusals.py @@ -14,7 +14,6 @@ from agent_comms.coordination_session import CoordinationSession from agent_comms.errors import RelationViolationError, ToolRefusedError from agent_comms.maintenance_barrier import MaintenanceBarrier -from agent_comms.store_files import SharedLock, _store_lock from agent_comms.typed_table import Column, DurableTables, Index, SharedFileTables, TypedTable from ..locked_store import RegistryFile @@ -45,8 +44,8 @@ def install(cls, store: Coordination) -> None: @classmethod def record(cls, root: Path, sender: str, target: str, error: ToolRefusedError) -> None: - with guard_original_root_write(root), _store_lock(root / "wire", mode=SharedLock()): - MaintenanceBarrier(RegistryFile.path_in(root)).assert_open_unlocked() + with (guard_original_root_write(root), + MaintenanceBarrier(RegistryFile.path_in(root)).allow_input()): with ( CoordinationSession(str(CoordinationStore.path_in(root))) as store, store.writing() as db, diff --git a/src/agent_comms/view_unread.py b/src/agent_comms/view_unread.py index 45dcf3112e..01838fe908 100644 --- a/src/agent_comms/view_unread.py +++ b/src/agent_comms/view_unread.py @@ -22,7 +22,6 @@ from .private_path import FileObservation, FileRevision, MissingFile from .read_ledger import ReadDocument, ReadLedger from .registry_document import RosterVisibility -from .store_files import SharedLock, _store_lock from .thread_owned_state import ThreadOwnedState from .typed_table import Column, JsonStorage, SQLiteTableName, SQLiteUserVersion, TypedRow, TypedTable from .ui_model.unread import CappedReplyUnread, IndexingUnread, ReplyUnread, UnreadPresentation @@ -341,16 +340,15 @@ def finished( completed = tuple(source for source, reader in self.readers.items() if reader.future.done()) if not completed: return self - # Publication stays under the existing deletion and SQLite owners. + # The state lock owns local retirement; SQLite owns publication exclusion. # A busy writer leaves these exact completed readers for the next observation. try: - with _store_lock(state.path.parent / "wire", mode=SharedLock(), blocking=False): - with (CoordinationReadUnavailable.checking(), - state.publishing_changes(database, state.index_path), - SQLiteFile.write_transaction(database)): - for source in completed: - self.readers[source].commit(database) - except (BlockingIOError, CoordinationReadUnavailable): + with (CoordinationReadUnavailable.checking(), + state.publishing_changes(database, state.index_path), + SQLiteFile.write_transaction(database)): + for source in completed: + self.readers[source].commit(database) + except CoordinationReadUnavailable: return self for source in completed: del self.readers[source] diff --git a/tests/test_held_sends.py b/tests/test_held_sends.py index e6b31a90bd..7b846f22f0 100644 --- a/tests/test_held_sends.py +++ b/tests/test_held_sends.py @@ -3,16 +3,51 @@ from agent_comms.bus import Bus import asyncio import os +import sqlite3 import sys +import time +from contextlib import closing from pathlib import Path import pytest from agent_comms.comms import Comms from agent_comms.coordination_tables.refusals import SendRefusal +from agent_comms.coordination_database import CoordinationStore +from agent_comms.errors import ToolRefusedError from agent_comms.maintenance_barrier import MaintenanceOperator +from agent_comms.store_files import _store_lock from agent_comms.tool_caller import ThreadCaller from agent_comms.tools import invoke_tool +from test_locked_store import event, process + + +def record_refusal(root, started): + started.set() + SendRefusal.record(root, "sender", "recipient", ToolRefusedError("retained refusal")) + + +def test_refusal_writer_keeps_registry_permission_until_its_sql_write_finishes(private_comms): + root = private_comms.root + started = event() + with closing(sqlite3.connect(CoordinationStore.path_in(root), isolation_level=None)) as writer: + writer.execute("BEGIN IMMEDIATE") + with process(record_refusal, root, started): + try: + assert started.wait(5) + deadline = time.monotonic() + 5 + while time.monotonic() < deadline: + try: + with _store_lock(private_comms.registry.store.path, blocking=False): + pass + except BlockingIOError: + break + time.sleep(.01) + else: + pytest.fail("The blocked refusal writer released its registry permission") + finally: + writer.execute("ROLLBACK") + assert tuple(row.reason for row in SendRefusal.recent(root)) == ("retained refusal",) @pytest.fixture From 85643b97be3e972f55f0989caf0eab2e47459bcd Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 23:12:47 -0400 Subject: [PATCH 07/17] Read executable physical bus references in deletion phase evidence --- plans/VIRTUAL-BUS-PLAN.md | 25 ++++++++++++- src/agent_comms/phase_evidence.py | 62 +++++++++++++++++++++++++++++++ src/agent_comms/plan_phases.py | 5 +-- tests/test_phase_evidence.py | 31 +++++++++++++++- 4 files changed, 118 insertions(+), 5 deletions(-) diff --git a/plans/VIRTUAL-BUS-PLAN.md b/plans/VIRTUAL-BUS-PLAN.md index 54286fdc42..e79e87cd13 100644 --- a/plans/VIRTUAL-BUS-PLAN.md +++ b/plans/VIRTUAL-BUS-PLAN.md @@ -88,10 +88,33 @@ Everything lands as one batch through the merge queue, drop-on-fail. Then one in Each phase, its predecessors and the evidence on `origin/main` that proves it done are declared in `src/agent_comms/plan_phases.py`; the commander's plan-idle rule and order refusals read that family, and accepting a task never marks a phase done. -- `git grep -n "offset\|max_bytes\|bus.jsonl\|bus_meta\|_wire_lock_path\|has_private_wire_fields\|/ \"wire\"" src` returns no bus hits (the last pattern catches the hand-spelled `root / "wire"` lock path at about 20 sites). +- `BusDeletionPhase.evidence` finds the five deleted modules absent and no known physical bus symbols, filename literals or lock-path expressions in Python source. Its AST check reads executable uses, including `/ "wire"`, `.with_name("wire")` and `.joinpath("wire")`, without matching prose or its own pattern declaration. This is a known-syntax screen; aliases and dynamically constructed paths still require source review. Remaining byte offsets and budgets are judged by their actual owner, since external native transcripts stay files. - The deleted modules are gone. - A process killed mid-publish leaves the whole message or none of it. - A reader that meets a held bus lock waits for it and never ends a turn (step C). Today `NativeSourceCursor._advance_proven` (`native_source_cursor.py:83`) takes the boundary with `blocking=False`, and with no contention allowance when no input is committed (`:79-80`). So any concurrent writer ends the turn with `BlockingIOError`: three times on core-5d8da75aa between 12:20 and 12:39 on 2026-10-21, with no install running. - Every history converts with every record round-tripping. - On every machine: Toad pages history, delivery wakes units, claims hold, and a real message to a live agent is answered. - Lines deleted are reported first; the census confirms. + +## Deletion checkpoint from the current integration branch + +At `acb2ddd0e`, all five old modules are already absent. Three physical bus locks +remain: `NativeReplyPreparation.finished`, `SendRefusal.record`, and +`CollaborationLedger.merge`. The last uses `.with_name("wire")`, which the old +grep misses. The old grep also matches its own `bus_meta` pattern declaration, +so its result does not establish whether physical bus uses remain. + +The deletion branch removes those locks through the existing owners. Unread +publication keeps its local preparation lock, source revision check and SQLite +write transaction; a busy SQLite writer leaves completed readers pending. +Refusal recording keeps maintenance permission under the registry lock until +its SQL transaction finishes. Ledger merge keeps the registered-author snapshot +through the existing document update, in the declared registry-before-ledger +lock order. None creates a replacement bus lock. + +The phase check reads committed source through `MainTree`. Its controls include +the actual remaining path spellings, physical filenames and removed symbols, +alongside comments and docstrings that describe deletion. This source checkpoint +does not mark the phase complete on main or qualify an installed runtime. The +merger still checks the affected copied native/Toad journey, including a fresh +DM, its actual reply and tab return. diff --git a/src/agent_comms/phase_evidence.py b/src/agent_comms/phase_evidence.py index 18289353ab..461e7f1136 100644 --- a/src/agent_comms/phase_evidence.py +++ b/src/agent_comms/phase_evidence.py @@ -10,6 +10,7 @@ import ast import copy +import re import subprocess from abc import ABC, abstractmethod from collections import Counter @@ -261,6 +262,67 @@ def describe(self) -> str: return f"{self.pattern!r} in {' '.join(self.paths)} on main" +class BusStorageAbsent(MainEvidence, ast.NodeVisitor): + """Read executable bus references, not text describing their deletion.""" + + symbols = re.compile(r"WireLog|wire_log|_wire_lock_path|has_private_wire_fields") + filenames = re.compile(r"bus[.]jsonl|bus_meta[.]json") + found = False + + def read(self, main: MainTree) -> bool: + scan = type(self)() + for tree in main.modules(("src",)): + scan.visit(tree) + return not scan.found + + def describe(self) -> str: + return "no known physical bus symbols, file literals or lock paths in Python source on main" + + def visit_ClassDef(self, node: ast.ClassDef) -> None: + self.found |= bool(self.symbols.fullmatch(node.name)) + self.generic_visit(node) + + def visit_FunctionDef(self, node: ast.FunctionDef) -> None: + self.found |= bool(self.symbols.fullmatch(node.name)) + self.generic_visit(node) + + def visit_ImportFrom(self, node: ast.ImportFrom) -> None: + self.found |= bool(self.symbols.fullmatch((node.module or "").rpartition(".")[2])) + self.generic_visit(node) + + def visit_Name(self, node: ast.Name) -> None: + self.found |= bool(self.symbols.fullmatch(node.id)) + + def visit_Attribute(self, node: ast.Attribute) -> None: + self.found |= bool(self.symbols.fullmatch(node.attr)) + self.generic_visit(node) + + def visit_alias(self, node: ast.alias) -> None: + self.found |= bool(self.symbols.fullmatch(node.name.rpartition(".")[2])) + + def visit_Constant(self, node: ast.Constant) -> None: + if isinstance(node.value, str): + self.found |= bool(self.filenames.fullmatch(node.value)) + + def visit_Expr(self, node: ast.Expr) -> None: + if isinstance(node.value, ast.Constant) and isinstance(node.value.value, str): + return # A docstring or inert prose cannot open a physical bus. + self.generic_visit(node) + + def visit_BinOp(self, node: ast.BinOp) -> None: + match node: + case ast.BinOp(op=ast.Div(), right=ast.Constant(value="wire")): + self.found = True + self.generic_visit(node) + + def visit_Call(self, node: ast.Call) -> None: + match node: + case ast.Call(func=ast.Attribute(attr="with_name" | "joinpath"), + args=[*_, ast.Constant(value="wire")]): + self.found = True + self.generic_visit(node) + + @dataclass(frozen=True) class AllEvidence(PhaseEvidence): parts: tuple[PhaseEvidence, ...] diff --git a/src/agent_comms/plan_phases.py b/src/agent_comms/plan_phases.py index 1c49d3b01b..5d1ed09e36 100644 --- a/src/agent_comms/plan_phases.py +++ b/src/agent_comms/plan_phases.py @@ -14,6 +14,7 @@ from agent_comms.declared_family import DeclaredFamily from agent_comms.phase_evidence import ( AllEvidence, + BusStorageAbsent, ClassFlagsAbsent, CommitMerged, NoAbsenceProbes, @@ -322,9 +323,7 @@ class BusDeletionPhase(VirtualBusPhase): "src/agent_comms/wire_record.py", ) ), - PatternAbsent( - r"bus\.jsonl|bus_meta|_wire_lock_path|has_private_wire_fields|/ \"wire\"" - ), + BusStorageAbsent(), ) ) diff --git a/tests/test_phase_evidence.py b/tests/test_phase_evidence.py index 480f9c2826..172d921176 100644 --- a/tests/test_phase_evidence.py +++ b/tests/test_phase_evidence.py @@ -27,7 +27,7 @@ UnreadableEvidenceError, ) from agent_comms.pinned_audit import GodFunction -from agent_comms.plan_phases import FLAG, ExternalBoundariesPhase, PlanPhase +from agent_comms.plan_phases import BusDeletionPhase, FLAG, ExternalBoundariesPhase, PlanPhase from agent_comms.repo import Repo from agent_comms.repository_name import RepositoryName @@ -100,6 +100,35 @@ def main_of(tmp_path, files): Repo(checkout=tmp_path, github=RepositoryName.from_wire("OpenHCSDev/agent-comms")))) +def test_bus_deletion_evidence_reads_uses_without_matching_its_own_declaration(tmp_path): + reading = main_of(tmp_path, { + "src/phase.py": "from agent_comms.phase_evidence import BusStorageAbsent\n" + "evidence = BusStorageAbsent()\n" + "# Delete bus.jsonl and bus_meta once the carry is accepted.\n" + "def documentation():\n 'bus.jsonl'\n", + }) + assert reading.holds(BusDeletionPhase.evidence) + # Every production spelling found during the owner trace must make the phase fail. + for source in ( + "class WireLog:\n pass\n", + "def has_private_wire_fields(value):\n return False\n", + "from somewhere.wire_log import WireLog\n", + "from somewhere.wire_log import another_name\n", + "def use(owner):\n return owner._wire_lock_path\n", + "def use(owner):\n return owner / 'wire'\n", + "def use(owner):\n return owner.with_name('wire')\n", + "def use(owner):\n return owner.joinpath('wire')\n", + "def use(owner):\n return owner / 'bus.jsonl'\n", + "def use(owner):\n return owner / 'bus_meta.json'\n", + ): + (tmp_path / "src/uses.py").write_text(source) + git(tmp_path, "add", ".") + git(tmp_path, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qm", "use") + git(tmp_path, "update-ref", "refs/remotes/origin/main", git(tmp_path, "rev-parse", "HEAD")) + changed = PlanReading((), MainTree.of(reading.main.repo)) + assert not changed.holds(BusDeletionPhase.evidence), source + + def test_source_shape_evidence_reads_declarations_on_main(tmp_path): copy = " def __init__(self, path):\n self.path = path\n" reading = main_of(tmp_path, { From 0d76980578eaacc3d93f16e0197edab2f8c1ffeb Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 23:16:43 -0400 Subject: [PATCH 08/17] Exercise backend declaration lifecycle without mutating sealed state --- plans/REMAINING-SLOP-PLAN.md | 8 +++ tests/test_agent_backend.py | 105 ++++++++++++++++++++++++++++------- 2 files changed, 92 insertions(+), 21 deletions(-) diff --git a/plans/REMAINING-SLOP-PLAN.md b/plans/REMAINING-SLOP-PLAN.md index c5c5009f0b..fae8eb3cee 100644 --- a/plans/REMAINING-SLOP-PLAN.md +++ b/plans/REMAINING-SLOP-PLAN.md @@ -158,6 +158,14 @@ run exposed an untyped session-file fixture, a stale caller roster and missing local Node dependencies; all three were corrected, without counting that run as green. Installed copied-agent DM/reply/tab-return qualification belongs to the later paired integration; this branch has not changed the live runtime. +Review corrected the declaration tests: they now re-execute the actual backend +declaration module through standard `runpy.run_path` in a fresh namespace, +declare real nominal subclasses and invoke the actual seal lifecycle. They do +not monkeypatch production sealing, registry contents or backend locations. +The abstract-class exclusion/no-rebind, competing-location and zero/two-default +cases pass (3 cases, 0.11s); fresh-entrypoint import checks also pass. Production +is unchanged from the 27-case qualification; the class census above describes +that implementation checkpoint before the test-fixture correction. The independent Pi producer/converter event comparison is retained; deriving expected producer events from the converters would erase its check. This slice does not close `RostersPhase` or edit its evidence declaration. diff --git a/tests/test_agent_backend.py b/tests/test_agent_backend.py index 68c19a6436..6bec560790 100644 --- a/tests/test_agent_backend.py +++ b/tests/test_agent_backend.py @@ -3,9 +3,10 @@ from __future__ import annotations import json +import runpy import sys from pathlib import Path -from types import MappingProxyType +from types import MappingProxyType, SimpleNamespace import pytest @@ -67,33 +68,95 @@ def test_runtime_location_round_trip_uses_declared_backend(session_file, locatio # -- declaration checks ------------------------------------------------------- -def test_incomplete_thread_backend_stays_abstract_and_cannot_rebind_a_location(monkeypatch): - original = PiSessionFile("saved-session").backend - with monkeypatch.context() as declarations: - declarations.setattr(ab.AgentBackend, "_sealed", False) +@pytest.fixture +def backend_declarations(): + # Re-execute the actual declaration module in a fresh namespace. The canonical + # package has already sealed its runtime family; none of that state is changed. + scope = SimpleNamespace(**runpy.run_path(ab.__file__, run_name=ab.__name__)) + + class Location(scope.RuntimeLocation): + def history_source(self, root, incarnation): + raise NotImplementedError("Declaration checks do not open history") + + def storage_files(self): + return () + + class RuntimeMethods(scope.AgentBackend, declared_name=None): + @classmethod + def for_worker(cls, runner, session_id): + return cls() + + @classmethod + def new_location(cls, root, worktree): + return cls.location_type() + + def run_turn(self, thread, content, delivery): + raise NotImplementedError("Declaration checks do not run turns") + + async def abort(self, owner): + raise NotImplementedError("Declaration checks do not own turns") + + async def close_idle(self): + pass - class IncompleteNative(PiNativeBackend, declared_name=None): - compact = ab.Compacts.compact + async def compact(self, thread, instructions): + return scope.NothingToCompaction() - with pytest.raises(TypeError, match="compact"): - IncompleteNative() - assert IncompleteNative not in ab.AgentBackend.members_with(ab.AgentBackend) - assert PiSessionFile("saved-session").backend is original + class Native(RuntimeMethods, scope.NewThreadBackend): + location_type = Location + event_kinds = frozenset() + + scope.RuntimeMethods = RuntimeMethods + scope.Native = Native + return scope + + +def test_incomplete_thread_backend_stays_abstract_and_cannot_rebind_a_location(backend_declarations): + scope = backend_declarations + original = PiSessionFile("saved-session").backend + + class IncompleteNative(scope.Native): + location_type = PiSessionFile + compact = scope.Compacts.compact + + with pytest.raises(TypeError, match="compact"): + IncompleteNative() + assert IncompleteNative not in scope.AgentBackend.members_with(scope.AgentBackend) + assert PiSessionFile("saved-session").backend is original + scope.AgentBackend.seal() + assert scope.Native.new_location(None, None).backend is scope.Native + assert scope.NewThreadBackend.selected() is scope.Native with pytest.raises(TypeError, match="run_turn"): - ab.ThreadServingBackend() + scope.ThreadServingBackend() + + +def test_sealing_rejects_competing_location_owners_before_attaching_any(backend_declarations): + scope = backend_declarations + + class Rival(scope.RuntimeMethods): + location_type = scope.Native.location_type + event_kinds = frozenset() + + assert scope.NewThreadBackend.selected() is scope.Native + with pytest.raises(scope.BackendDeclarationError, match="belongs to several backends"): + scope.AgentBackend.seal() + with pytest.raises(AttributeError): + scope.Native.new_location(None, None).backend -def test_sealing_rejects_competing_location_owners_before_attaching_any(monkeypatch): - monkeypatch.setattr(PiDurableBackend, "location_type", PiSessionFile) - with pytest.raises(ab.BackendDeclarationError, match="belongs to several backends"): - ab.AgentBackend.seal() - assert PiSessionFile("saved-session").backend is PiNativeBackend +def test_new_thread_selection_requires_one_concrete_nominal_member(backend_declarations): + empty = SimpleNamespace(**runpy.run_path(ab.__file__, run_name=ab.__name__)) + with pytest.raises(empty.BackendDeclarationError, match="exactly one backend"): + empty.NewThreadBackend.selected() + scope = backend_declarations + class SecondDefault(scope.Native): + pass -def test_new_thread_selection_requires_one_concrete_nominal_member(monkeypatch): - monkeypatch.setattr(ab.AgentBackend, "__registry__", {"pi_durable": PiDurableBackend}) - with pytest.raises(ab.BackendDeclarationError, match="exactly one backend"): - ab.NewThreadBackend.selected() + with pytest.raises(scope.BackendDeclarationError, match="exactly one backend"): + scope.NewThreadBackend.selected() + with pytest.raises(scope.BackendDeclarationError, match="exactly one backend"): + scope.AgentBackend.seal() class _ProbeEvent(DeclaredFamily): From 194957ee5cd79ac87aba62d8f7a6d3b860af0c97 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 23:16:44 -0400 Subject: [PATCH 09/17] Separate historical cursor contention incident from current owner path --- plans/VIRTUAL-BUS-PLAN.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plans/VIRTUAL-BUS-PLAN.md b/plans/VIRTUAL-BUS-PLAN.md index e79e87cd13..5cde521fca 100644 --- a/plans/VIRTUAL-BUS-PLAN.md +++ b/plans/VIRTUAL-BUS-PLAN.md @@ -91,11 +91,19 @@ Each phase, its predecessors and the evidence on `origin/main` that proves it do - `BusDeletionPhase.evidence` finds the five deleted modules absent and no known physical bus symbols, filename literals or lock-path expressions in Python source. Its AST check reads executable uses, including `/ "wire"`, `.with_name("wire")` and `.joinpath("wire")`, without matching prose or its own pattern declaration. This is a known-syntax screen; aliases and dynamically constructed paths still require source review. Remaining byte offsets and budgets are judged by their actual owner, since external native transcripts stay files. - The deleted modules are gone. - A process killed mid-publish leaves the whole message or none of it. -- A reader that meets a held bus lock waits for it and never ends a turn (step C). Today `NativeSourceCursor._advance_proven` (`native_source_cursor.py:83`) takes the boundary with `blocking=False`, and with no contention allowance when no input is committed (`:79-80`). So any concurrent writer ends the turn with `BlockingIOError`: three times on core-5d8da75aa between 12:20 and 12:39 on 2026-10-21, with no install running. +- Concurrent bus publication must not abort native cursor settlement. The current path is `CoordinatedTurn.cursor_status_for` → `CursorDelivery.advance` → `CursorOwner.advance_async` → `CursorOwner._advance_owned`. Bus reads use `Bus.observing` and SQLite snapshots; the cursor owner commits through the coordinator session's existing write transaction. `Coordination.run_async` runs this work through its worker with the default `StoreWait` contention policy. Qualify that path under competing writers within its bounded wait; no physical bus lock is reintroduced. - Every history converts with every record round-tripping. - On every machine: Toad pages history, delivery wakes units, claims hold, and a real message to a live agent is answered. - Lines deleted are reported first; the census confirms. +Historical regression evidence: on core-5d8da75aa, the former +`NativeSourceCursor._advance_proven` in `native_source_cursor.py` took its +boundary with `blocking=False`, without contention allowance when no input +was committed. Concurrent writers ended turns with `BlockingIOError` three +times between 12:20 and 12:39 on 2026-10-21, with no install running. That +class and file have since been deleted; the incident motivates the current +contention obligation above, rather than describing a current source site. + ## Deletion checkpoint from the current integration branch At `acb2ddd0e`, all five old modules are already absent. Three physical bus locks From e92b742ef6347c20c4662361e7783c09d1553e5b Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 23:30:49 -0400 Subject: [PATCH 10/17] Keep independent Pi event ABI coverage in roster plan --- plans/REMAINING-SLOP-PLAN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plans/REMAINING-SLOP-PLAN.md b/plans/REMAINING-SLOP-PLAN.md index 573f992bc3..a68133cdcd 100644 --- a/plans/REMAINING-SLOP-PLAN.md +++ b/plans/REMAINING-SLOP-PLAN.md @@ -134,7 +134,7 @@ Three audits, one per catalog rung group, each finding read in the code and chec **6.4 Rosters and registries beside families (MEMB-1/2/4, about 120).** - Five hand-rolled `__init_subclass__` registries: `Carry.declared` (`installer/carry.py:45-54`), `ThreadRename.kinds` (`thread_management.py:557-567`, gone with 4.1), `BirthlessIncarnation.members` (`thread_identity.py:185-192`), `CliToolArguments.types` (`tool_calls.py:89-95`), `_start_phases`/`_end_events` (`pi_events.py:59-93`): `DeclaredFamily` or attributes through the MRO. -- `PI_0_85_1_EVENT_KINDS`, 36 names re-typed beside `NativePiEvent.known_kinds()` (`pi_native_backend.py:63-81`, check at `agent_backend.py:428`): derive the union; the equality check goes. +- Preserve the independent Pi producer ABI contract: `PI_0_85_1_EVENT_KINDS` in `pi_native_backend.py` declares the pinned runtime's emitted kinds, while `NativePiEvent.known_kinds()` declares converter coverage. `AgentBackend.require_event_family` compares these independent answers and reports missing or extra converters. Deriving the expected producer kinds from converters would erase this check. Refactor the internal `_start_phases`/`_end_events` registration lifecycle without weakening producer/converter coverage; removal of the external manifest is not a completion condition. - `THREAD_CAPABILITIES` plus `serves_threads`/`serves_new_threads` flags plus `validate_backends` probing with `getattr` (`agent_backend.py:358-466`): `ThreadServingBackend(AgentBackend, Steers, Compacts)` and `NewThreadBackend`; ABC instantiation enforces the roles. - Display names re-typed from class names in about 40 places (`tools.py` labels, `notification_progress.py:45-157`, `todos.py:1502/2012/2088/2605/2675`, `display_order.py:76-136`, `relationships.py:382/397`, `turn_phase.py:169/218`, `config_options.py:192-194`, `input_attempt.py:400/440/447` `public_status`): derived from the declared name, literal only where the text differs. - Sigils `#`/`@` spelled by hand at about 22 sites (`catalog_document.py:61-232`, `channels.py`, `channel_targets.py`, `goal_presentation.py:71-72`, `goal_actions.py:305/318`, …): owned by `Tag.channel` and `ThreadTarget.mention`. @@ -226,7 +226,7 @@ Waves 4 to 8 were written on 2026-10-21 and declared no phase in `plan_phases.py | wire_renames | 4.6 | no `WireName("admission…` in src | one unit, Toad in lockstep | | owned_dispatch | 5.4 | no `TurnInboxReceiver`, no hand `separators=(",", ":")` | one unit | | kind_families | 6.3 | no `TagAction`, `ExecutionOrigin`, `WhenBusy`, `ControlClassification`, `MembershipChange` or `ViewKind` enum classes | by file | -| rosters | 6.4 | no `PI_0_85_1_EVENT_KINDS`, `THREAD_CAPABILITIES` or `serves_new_threads` | one unit | +| rosters | 6.4 | internal registries and capability/name/path rosters derive from their owners; retain independent Pi producer/converter coverage | by family; shared phase proof has one owner | | records | 7 | no `ConnectivityFacet`/`observe_connectivity`; no `nonlocal` in the five document updaters | one unit | 9.4 is Tristan's ruleset change. 9.5, 9.6, 9.9, 9.10 and 9.11 are claude-ai's and are measured by their own numbers in this section: a touched-tests PR check, the full ratchet list in the job summary, commander calls under 3 s, one GitHub reader, and one stack lock. The order across plans stays Tristan's: the virtual bus batch first, then multiple inheritance, the commander-native plan, then this plan. Phases with no stored data run in parallel with all of them on disjoint files. From d3d57698dcf65c5a3503abe63d51b549763e7792 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 23:32:30 -0400 Subject: [PATCH 11/17] Judge roster phase by internal declarations and retain producer ABI --- src/agent_comms/phase_evidence.py | 5 ++++- src/agent_comms/plan_phases.py | 18 ++++++++++++++--- tests/test_phase_evidence.py | 33 ++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/agent_comms/phase_evidence.py b/src/agent_comms/phase_evidence.py index 18289353ab..672cc84c8f 100644 --- a/src/agent_comms/phase_evidence.py +++ b/src/agent_comms/phase_evidence.py @@ -497,6 +497,7 @@ class ClassFlagsAbsent(MainEvidence): pattern: str paths: tuple[str, ...] = ("src",) + class_names: tuple[str, ...] = () def read(self, main: MainTree) -> bool: matched = main.matching_lines(self.pattern, self.paths) @@ -504,11 +505,13 @@ def read(self, main: MainTree) -> bool: (path, statement.lineno) in matched for path in {path for path, _ in matched} for declared in Classes.of((main.module(path).tree,)) + if not self.class_names or declared.name in self.class_names for statement in declared.body ) def describe(self) -> str: - return f"no class body line {self.pattern!r} in {' '.join(self.paths)} on main" + owners = f" of {', '.join(self.class_names)}" if self.class_names else "" + return f"no class body line {self.pattern!r}{owners} in {' '.join(self.paths)} on main" class NamedClasses(ast.NodeVisitor): diff --git a/src/agent_comms/plan_phases.py b/src/agent_comms/plan_phases.py index 1c49d3b01b..6f69c8bbad 100644 --- a/src/agent_comms/plan_phases.py +++ b/src/agent_comms/plan_phases.py @@ -652,9 +652,21 @@ class KindFamiliesPhase(RemainingSlopPhase): class RostersPhase(RemainingSlopPhase): - """6.4: rosters beside families are derived from the family.""" - - evidence = PatternAbsent(r"PI_0_85_1_EVENT_KINDS|THREAD_CAPABILITIES|serves_new_threads") + """6.4: remove the known internal registries; external producer ABI stays independent.""" + + evidence = AllEvidence(( + PatternAbsent(r"^\s*THREAD_CAPABILITIES\s*[:=]"), + ClassFlagsAbsent(r"^\s*serves_(new_)?threads\s*[:=]"), + *(ClassFlagsAbsent(r"^\s*" + attribute + r"\s*[:=]", class_names=(owner,)) + for owner, attribute in ( + ("Carry", "declared"), + ("ThreadRename", "kinds"), + ("BirthlessIncarnation", "members"), + ("CliToolArguments", "types"), + ("PiTurnPhase", "_start_phases"), + ("PiTurnPhase", "_end_events"), + )), + )) class RecordsPhase(RemainingSlopPhase): diff --git a/tests/test_phase_evidence.py b/tests/test_phase_evidence.py index 480f9c2826..b4b84e2089 100644 --- a/tests/test_phase_evidence.py +++ b/tests/test_phase_evidence.py @@ -1,4 +1,5 @@ import subprocess +from pathlib import Path from agent_comms.field_codec import FieldCodec from agent_comms.head_review import HeadReview @@ -27,7 +28,7 @@ UnreadableEvidenceError, ) from agent_comms.pinned_audit import GodFunction -from agent_comms.plan_phases import FLAG, ExternalBoundariesPhase, PlanPhase +from agent_comms.plan_phases import FLAG, ExternalBoundariesPhase, PlanPhase, RostersPhase from agent_comms.repo import Repo from agent_comms.repository_name import RepositoryName @@ -100,6 +101,36 @@ def main_of(tmp_path, files): Repo(checkout=tmp_path, github=RepositoryName.from_wire("OpenHCSDev/agent-comms")))) +def test_roster_evidence_keeps_external_abi_and_each_remaining_internal_registry(tmp_path): + source = Path(__file__).parents[1] / "src/agent_comms" + reading = main_of(tmp_path, { + "src/phase_evidence.py": (source / "phase_evidence.py").read_text(), + "src/plan_phases.py": (source / "plan_phases.py").read_text(), + "src/producer.py": "PI_0_85_1_EVENT_KINDS = frozenset({'alpha', 'beta'})\n" + "class Runtime:\n event_kinds = PI_0_85_1_EVENT_KINDS\n", + "src/unrelated.py": "class OtherOwner:\n members = {}\n" + "def local():\n types = {}\n", + }) + assert reading.holds(RostersPhase.evidence) + for remaining in ( + "THREAD_CAPABILITIES: tuple[type, ...] = (Steers, Compacts)\n", + "class Backend:\n serves_threads = True\n", + "class Backend:\n serves_new_threads: ClassVar[bool] = True\n", + "class Carry:\n declared: ClassVar[dict] = {}\n", + "class ThreadRename:\n kinds = {}\n", + "class BirthlessIncarnation:\n members = {}\n", + "class CliToolArguments:\n types = {}\n", + "class PiTurnPhase:\n _start_phases = {}\n", + "class PiTurnPhase:\n _end_events = {}\n", + ): + (tmp_path / "src/remaining.py").write_text(remaining) + git(tmp_path, "add", ".") + git(tmp_path, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qm", "registry") + git(tmp_path, "update-ref", "refs/remotes/origin/main", git(tmp_path, "rev-parse", "HEAD")) + changed = PlanReading((), MainTree.of(reading.main.repo)) + assert not changed.holds(RostersPhase.evidence), remaining + + def test_source_shape_evidence_reads_declarations_on_main(tmp_path): copy = " def __init__(self, path):\n self.path = path\n" reading = main_of(tmp_path, { From 98a2305f119de92ba64a3f3220031fc33bf17c70 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 23:35:22 -0400 Subject: [PATCH 12/17] Keep committed-tree grep within its repository owner --- src/agent_comms/phase_evidence.py | 9 +++++++-- tests/test_phase_evidence.py | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/agent_comms/phase_evidence.py b/src/agent_comms/phase_evidence.py index 672cc84c8f..2ac4cbbdff 100644 --- a/src/agent_comms/phase_evidence.py +++ b/src/agent_comms/phase_evidence.py @@ -43,8 +43,13 @@ def has(self, path: str) -> bool: return self.git("cat-file", "-e", f"{self.commit}:{path}").returncode == 0 def grep(self, *options: str, pattern: str, paths: tuple[str, ...]) -> list[str]: - """``git grep -E`` rows under ``paths``; git pathspecs such as ``:!file`` exclude.""" - run = self.git("grep", *options, "-E", "-e", pattern, self.commit, "--", *paths) + """``git grep -E`` rows in this tree, like ``module`` and ``modules``. + + A submodule is another repository's tree, regardless of local recurse settings. + Git pathspecs such as ``:!file`` exclude. + """ + run = self.git("grep", "--no-recurse-submodules", *options, + "-E", "-e", pattern, self.commit, "--", *paths) if run.returncode not in (0, 1): run.check_returncode() return run.stdout.splitlines() diff --git a/tests/test_phase_evidence.py b/tests/test_phase_evidence.py index b4b84e2089..35e70ab1a8 100644 --- a/tests/test_phase_evidence.py +++ b/tests/test_phase_evidence.py @@ -131,6 +131,26 @@ def test_roster_evidence_keeps_external_abi_and_each_remaining_internal_registry assert not changed.holds(RostersPhase.evidence), remaining +def test_main_tree_grep_uses_the_same_repository_tree_as_its_module_reader(tmp_path): + dependency = tmp_path / "dependency" + dependency.mkdir() + main_of(dependency, {"src/debt.py": "class Carry:\n declared = {}\n"}) + checkout = tmp_path / "owner" + checkout.mkdir() + reading = main_of(checkout, {"src/owner.py": "class OtherOwner:\n members = {}\n"}) + git(checkout, "-c", "protocol.file.allow=always", "submodule", "add", "-q", + str(dependency), "src/dependency") + git(checkout, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qam", "dependency") + git(checkout, "update-ref", "refs/remotes/origin/main", git(checkout, "rev-parse", "HEAD")) + git(checkout, "config", "submodule.recurse", "true") + assert "src/dependency" in git(checkout, "grep", "--recurse-submodules", "-n", + "-e", "declared", "HEAD", "--", "src") + current = PlanReading((), MainTree.of(reading.main.repo)) + assert current.holds(RostersPhase.evidence) + assert not current.main.matching_lines(r"declared", ("src",)) + assert len(tuple(current.main.modules(("src",)))) == 1 + + def test_source_shape_evidence_reads_declarations_on_main(tmp_path): copy = " def __init__(self, path):\n self.path = path\n" reading = main_of(tmp_path, { From b29df733b0375f52e6c1f2acfd12ade9272d6d1f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 23:40:03 -0400 Subject: [PATCH 13/17] Register CLI argument shapes through existing wire metaclass --- plans/REMAINING-SLOP-PLAN.md | 41 +++++++++- src/agent_comms/tool_calls.py | 27 ++++--- tests/test_cli_tool_arguments.py | 125 +++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 13 deletions(-) create mode 100644 tests/test_cli_tool_arguments.py diff --git a/plans/REMAINING-SLOP-PLAN.md b/plans/REMAINING-SLOP-PLAN.md index a68133cdcd..5b1e4ea04d 100644 --- a/plans/REMAINING-SLOP-PLAN.md +++ b/plans/REMAINING-SLOP-PLAN.md @@ -133,13 +133,50 @@ Three audits, one per catalog rung group, each finding read in the code and chec - `RecoveryCondition` matching a lifecycle by `declared_name` string equality across two families (`recovery_states.py:21-26`): `issubclass`. **6.4 Rosters and registries beside families (MEMB-1/2/4, about 120).** -- Five hand-rolled `__init_subclass__` registries: `Carry.declared` (`installer/carry.py:45-54`), `ThreadRename.kinds` (`thread_management.py:557-567`, gone with 4.1), `BirthlessIncarnation.members` (`thread_identity.py:185-192`), `CliToolArguments.types` (`tool_calls.py:89-95`), `_start_phases`/`_end_events` (`pi_events.py:59-93`): `DeclaredFamily` or attributes through the MRO. +- Four remaining hand-rolled `__init_subclass__` registration owners: `Carry.declared` (`installer/carry.py:42-58`), `ThreadRename.kinds` (`thread_management.py`; still present, not removed by 4.1 at this integration base), `BirthlessIncarnation.members` (`thread_identity.py:214-226`), and `_start_phases`/`_end_events` (`pi_events.py:59-93`). Move registration through the existing nominal mechanisms or derive behavior through the MRO; preserve each boundary's actual keys and consumers. `CliToolArguments.types` and its private hook are removed by the CLI argument slice below. - Preserve the independent Pi producer ABI contract: `PI_0_85_1_EVENT_KINDS` in `pi_native_backend.py` declares the pinned runtime's emitted kinds, while `NativePiEvent.known_kinds()` declares converter coverage. `AgentBackend.require_event_family` compares these independent answers and reports missing or extra converters. Deriving the expected producer kinds from converters would erase this check. Refactor the internal `_start_phases`/`_end_events` registration lifecycle without weakening producer/converter coverage; removal of the external manifest is not a completion condition. -- `THREAD_CAPABILITIES` plus `serves_threads`/`serves_new_threads` flags plus `validate_backends` probing with `getattr` (`agent_backend.py:358-466`): `ThreadServingBackend(AgentBackend, Steers, Compacts)` and `NewThreadBackend`; ABC instantiation enforces the roles. +- Backend role slice #2623 is published and reviewed, awaiting integration: it removes `THREAD_CAPABILITIES`, the serving flags, `validate_backends` and copied `unstarted_type` declarations. `ThreadServingBackend(Steers, Compacts, AgentBackend)` puts capability bases first so `AgentBackend`'s concrete refusal method cannot hide the abstract turn obligation; `NewThreadBackend.selected` derives the unique default. Preserve the independent producer/converter event comparison. - Display names re-typed from class names in about 40 places (`tools.py` labels, `notification_progress.py:45-157`, `todos.py:1502/2012/2088/2605/2675`, `display_order.py:76-136`, `relationships.py:382/397`, `turn_phase.py:169/218`, `config_options.py:192-194`, `input_attempt.py:400/440/447` `public_status`): derived from the declared name, literal only where the text differs. - Sigils `#`/`@` spelled by hand at about 22 sites (`catalog_document.py:61-232`, `channels.py`, `channel_targets.py`, `goal_presentation.py:71-72`, `goal_actions.py:305/318`, …): owned by `Tag.channel` and `ThreadTarget.mention`. - Store and file names restated outside their class: `root / "wire"` in about 20 places across 17 modules (the virtual bus plan's done-grep for `_wire_lock_path` misses every `_store_lock(root / "wire")`; its grep gains that pattern), `~/.local/state/agent-comms` six times, bridge inbox dirs, `"registry.json"` at `installer/upgrade.py:566`, `".registry-owner-guard"` at `goal_history.py:208`, `active-route.json` five times, Pi `auth.json` in four modules: `X.path_in(root)` and one state-home owner. +CLI argument slice (#2626, base `acb2ddd0e`, 2026-10-23): the existing +`RegisteredWireMeta` now owns argument-model registration after Pydantic/ABC +construction. Each concrete declaration supplies its genuine external +`cli_names` tuple as the registry key; decode derives matching shapes from +that registry without retaining another alias map. Both production consumers, +Claude `ToolUse.call` and OpenCode `ToolState.call`, keep the shared decode +entrypoint. The seven shapes retain all 13 case-sensitive external spellings, +extra input, canonical previews and edit patches. Unknown names and invalid +arguments remain the original call; validation refusal belongs to its runtime. +Abstract specializations cannot overwrite concrete aliases. The existing +registry rejects duplicate tuples at declaration; overlapping nonidentical +tuples raise an explicit ambiguity at decode, before argument validation can +turn the conflict into an ordinary refused call. Overlap is not checked at +declaration. A new shape still needs only its class and alias declaration; +the improvement is shared declaration lifecycle and collision behavior, not +a claimed reduction from multiple authored rosters. + +NRA before/after source receipts cover Core/hooks/tests/tools, paired Toad and +the installed metaclass-registry/Pydantic source: 1,643/1,644 modules, +7,434/7,438 original classes, 7,144 projected classes in each, zero parse +omissions. The 290/294 unprojected originals remain explicit; the four added +classes are function-local declaration controls. Dynamic source projection +does not prove their runtime behavior. The compact index also leaves the +`AutoRegisterMeta` package re-export unresolved as a base of +`RegisteredWireMeta`; source inspection and a fresh-process runtime MRO read +confirm its composition with `ModelMetaclass`. Focused source checks pass 31 cases +(0.63s), exercising real Claude/OpenCode models, edit presentation and actual +fresh declaration lifecycles without patching registry state. The first run +passed 26 and failed two new assertions that incorrectly put call identity on +search preview records; identity is checked on its owning `ToolCall` and +emitted transcript instead. Receipts remain in the owned +`cli-tool-argument-family` scratch directory. Fresh CLI/UI-model import checks +also pass (two cases, 3.73s). This slice is a published draft, +not merged, installed or live-qualified. Shared phase proof belongs to its bus +owner; the other registries and the remaining name/sigil/path work keep 6.4 +open. + ### Wave 7: records where parameter lists and dicts stand in - `ManagedLaunchRequest`: nine parameters declared in seven signatures and forwarded six times (`native_child.py:222/341`, `native_pi.py:491/536`, `native_session_prepare.py:67/88`, `backend.py:112`, `stream_agent_events` has 23), with one lock-launch-fence procedure written twice. About 60. diff --git a/src/agent_comms/tool_calls.py b/src/agent_comms/tool_calls.py index 8f75af3a04..8c14d6f287 100644 --- a/src/agent_comms/tool_calls.py +++ b/src/agent_comms/tool_calls.py @@ -11,11 +11,12 @@ from abc import abstractmethod from collections.abc import Mapping, Sequence from dataclasses import dataclass, replace -from typing import ClassVar, Unpack +from typing import ClassVar -from pydantic import AliasChoices, ConfigDict, Field, JsonValue, ValidationError +from metaclass_registry import RegistryConfig +from pydantic import AliasChoices, Field, JsonValue, ValidationError -from .cli_protocol import WireModel +from .cli_protocol import RegisteredWireMeta, WireModel from .field_codec import FieldCodec, MalformedRecord from .native_tools import BashTool, EditTool, GlobTool, GrepTool, ReadTool, WriteTool from .tool_declaration import ReplacementEdit, ToolDeclaration @@ -81,27 +82,31 @@ def ended(self, text: str, ok: bool, position: int | str, timestamp: float) -> T return replace(super().ended(text, ok, position, timestamp), diff=self.diff) -class CliToolArguments(WireModel): +class CliToolArguments(WireModel, metaclass=RegisteredWireMeta): """A runtime's arguments for one Core tool in its own spelling (Claude Code's, OpenCode's), read once into the Core record; the runtime's other keys stay in its raw input.""" cli_names: ClassVar[tuple[str, ...]] = () - types: ClassVar[dict[str, type[CliToolArguments]]] = {} + __registry_config__: ClassVar[RegistryConfig] = RegistryConfig( + registry_dict={}, key_attribute="cli_names", registry_name="CLI tool arguments", + ) + __registry__: ClassVar[dict[tuple[str, ...], type[CliToolArguments]]] model_config = {"extra": "allow"} - def __init_subclass__(cls, **kwargs: Unpack[ConfigDict]) -> None: - super().__init_subclass__(**kwargs) - cls.types.update(dict.fromkeys(cls.cli_names, cls)) - @classmethod def decode(cls, sent: ToolCall, arguments: Mapping[str, JsonValue]) -> ToolCall: """The call decoded when its name has a declared shape that its values fit, else as sent. The model can send values that do not fit (Claude refuses them with an error result).""" - if sent.name not in cls.types: + shapes = [shape for names, shape in cls.__registry__.items() if sent.name in names] + if not shapes: return sent + if len(shapes) > 1: + owners = ", ".join(shape.__name__ for shape in shapes) + raise ValueError(f"Ambiguous CLI tool name {sent.name!r}: {owners}") + shape, = shapes try: - return cls.types[sent.name].model_validate(arguments).call(sent.id) + return shape.model_validate(arguments).call(sent.id) except ValidationError, MalformedRecord: # kept as sent; its runtime answers the refusal return sent diff --git a/tests/test_cli_tool_arguments.py b/tests/test_cli_tool_arguments.py new file mode 100644 index 0000000000..a54bd41e19 --- /dev/null +++ b/tests/test_cli_tool_arguments.py @@ -0,0 +1,125 @@ +"""External tool spellings select one declared shape; refused calls stay as sent.""" + +import runpy +from abc import abstractmethod +from types import SimpleNamespace + +import pytest +from metaclass_registry import DuplicateRegistryKeyError + +from agent_comms import tool_calls +from agent_comms.cli_bridges.claude_protocol import ToolUse +from agent_comms.cli_bridges.opencode_parts import ToolPart +from agent_comms.native_tools import BashTool, EditTool, GlobTool, GrepTool, ReadTool, WriteTool +from agent_comms.tool_calls import CliToolArguments, ToolCall + + +@pytest.mark.parametrize('name,arguments,tool', [ + ('bash', {'command': 'pwd'}, BashTool), + ('grep', {'pattern': 'needle'}, GrepTool), + ('glob', {'pattern': '*.py'}, GlobTool), + ('read', {'filePath': 'owner.py'}, ReadTool), + ('edit', {'filePath': 'owner.py', 'oldString': 'before', 'newString': 'after'}, EditTool), + ('write', {'filePath': 'owner.py', 'content': 'after'}, WriteTool), +]) +def test_opencode_spellings_reach_native_records_with_extra_input(name, arguments, tool): + arguments = {**arguments, 'provider_detail': ['retained']} + part = ToolPart.model_validate({ + 'type': 'tool', 'id': 'part', 'sessionID': 'session', 'messageID': 'message', + 'callID': 'call', 'tool': name, 'state': {'status': 'pending', 'input': arguments}, + }) + call = part.state.call(part) + assert type(call.tool) is tool + assert call.id == 'call' + assert call.arguments['provider_detail'] == ['retained'] + started = call.started('part', 1.0) + assert started.tool_call_id == call.id + assert started.tool == call.tool + + +def test_claude_glob_spelling_uses_its_own_search_record(): + call = ToolUse(id='call', name='Glob', input={'pattern': '*.py'}).call() + assert call.tool == GlobTool(pattern='*.py') + + +@pytest.mark.parametrize('name,arguments', [ + ('mcp__custom', {'private': ['untouched']}), + ('BASH', {'command': 'pwd'}), + ('Glob', {}), + ('Bash', {'command': 17}), + ('Grep', {'pattern': 'needle', 'path': 17}), +]) +def test_unknown_or_invalid_calls_remain_the_same_original_object(name, arguments): + sent = ToolCall('refused', name, arguments) + assert CliToolArguments.decode(sent, arguments) is sent + assert ToolUse(id=sent.id, name=name, input=arguments).call() == sent + + +@pytest.mark.parametrize('name,arguments', [ + ('external_custom', {'private': ['untouched']}), + ('glob', {}), + ('bash', {'command': 17}), +]) +def test_opencode_unknown_or_invalid_calls_stay_as_sent(name, arguments): + part = ToolPart.model_validate({ + 'type': 'tool', 'id': 'part', 'sessionID': 'session', 'messageID': 'message', + 'callID': 'call', 'tool': name, 'state': {'status': 'pending', 'input': arguments}, + }) + assert part.state.call(part) == ToolCall('call', name, arguments) + + +@pytest.fixture +def argument_family(): + # Execute the actual declaration lifecycle; the canonical family stays untouched. + return SimpleNamespace(**runpy.run_path(tool_calls.__file__, run_name=tool_calls.__name__)) + + +def test_new_shape_is_discovered_from_its_alias_declaration(argument_family): + class InspectArguments(argument_family.CliToolArguments): + cli_names = ('Inspect', 'inspect') + path: str + + def record(self, *, call_id: str) -> ReadTool: + return ReadTool(call_id=call_id, path=self.path) + + for name in InspectArguments.cli_names: + arguments = {'path': 'new-owner.py'} + sent = argument_family.ToolCall('new', name, arguments) + assert argument_family.CliToolArguments.decode(sent, arguments).tool == ReadTool( + call_id='new', path='new-owner.py', + ) + + +def test_abstract_specialization_cannot_take_a_concrete_alias(argument_family): + class IncompleteRead(argument_family.CliReadArguments): + @abstractmethod + def unfinished(self): ... + + with pytest.raises(TypeError, match='abstract'): + IncompleteRead(file_path='owner.py') + arguments = {'file_path': 'owner.py'} + sent = argument_family.ToolCall('read', 'Read', arguments) + assert argument_family.CliToolArguments.decode(sent, arguments).tool == ReadTool( + call_id='read', path='owner.py', + ) + + +def test_duplicate_alias_group_is_rejected_by_declaration(argument_family): + with pytest.raises(DuplicateRegistryKeyError): + class CompetingRead(argument_family.CliReadArguments): + pass + + +def test_overlapping_alias_groups_cannot_silently_choose_an_owner(argument_family): + class OverlappingRead(argument_family.CliReadArguments): + cli_names = ('Read', 'ReadAgain') + + for arguments in ({'file_path': 'owner.py'}, {}): + sent = argument_family.ToolCall('ambiguous', 'Read', arguments) + with pytest.raises(ValueError, match="Ambiguous CLI tool name 'Read'.*CliReadArguments.*OverlappingRead"): + argument_family.CliToolArguments.decode(sent, arguments) + arguments = {'file_path': 'owner.py'} + sent = argument_family.ToolCall('unique', 'ReadAgain', arguments) + assert argument_family.CliToolArguments.decode(sent, arguments).tool == ReadTool( + call_id='unique', path='owner.py', + ) From c921abad3cf1000b56ce31e063c555465539d1c2 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 01:39:16 -0400 Subject: [PATCH 14/17] Use existing registry lifecycle for birthless identities --- src/agent_comms/thread_identity.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/agent_comms/thread_identity.py b/src/agent_comms/thread_identity.py index 71af5ff476..d849a130a6 100644 --- a/src/agent_comms/thread_identity.py +++ b/src/agent_comms/thread_identity.py @@ -14,6 +14,7 @@ from functools import cached_property from typing import TYPE_CHECKING, Callable, ClassVar, Self, TypeVar, Annotated +from metaclass_registry import AutoRegisterMeta, RegistryConfig from agent_comms.participant_identity import ParticipantIdentity from .agent_backend import AgentBackend, RuntimeLocation @@ -211,19 +212,16 @@ def commanding_identity(self) -> ThreadIncarnation: return self.authority -class BirthlessIncarnation(ThreadIncarnation): +class BirthlessIncarnation(ThreadIncarnation, metaclass=AutoRegisterMeta): """A member whose wire birth is a declared mark, not recorded evidence; never a lookup. Each member declares its mark in ``WIRE_BIRTH``; the record decodes a mark to its member.""" WIRE_BIRTH: ClassVar[float] - members: ClassVar[dict[float, type[BirthlessIncarnation]]] = {} - - def __init_subclass__(cls, **kwargs: object) -> None: - super().__init_subclass__(**kwargs) - if cls.WIRE_BIRTH in BirthlessIncarnation.members: - raise TypeError(f"Wire birth {cls.WIRE_BIRTH} is already declared") - BirthlessIncarnation.members[cls.WIRE_BIRTH] = cls + __registry_config__: ClassVar[RegistryConfig] = RegistryConfig( + registry_dict={}, key_attribute="WIRE_BIRTH", registry_name="birthless incarnation", + ) + __registry__: ClassVar[dict[float, type[BirthlessIncarnation]]] @property def wire_birth(self) -> float: @@ -357,8 +355,8 @@ class ThreadIncarnationRecord: created_at: float def incarnation(self) -> ThreadIncarnation: - if self.created_at in BirthlessIncarnation.members: - return BirthlessIncarnation.members[self.created_at].recorded_as(self.name) + if member := BirthlessIncarnation.__registry__.get(self.created_at): + return member.recorded_as(self.name) return RecordedThreadIncarnation(self.name, self.created_at) @classmethod From 067532a10492dffd97bd3060766a35d6a30feec2 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 01:43:59 -0400 Subject: [PATCH 15/17] Exercise birth marker decoding and declaration lifecycle --- tests/test_birthless_declarations.py | 88 ++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/test_birthless_declarations.py diff --git a/tests/test_birthless_declarations.py b/tests/test_birthless_declarations.py new file mode 100644 index 0000000000..f7f1ef4438 --- /dev/null +++ b/tests/test_birthless_declarations.py @@ -0,0 +1,88 @@ +"""Only concrete birth-marker declarations decode; external identity spelling stays fixed.""" + +import runpy +from abc import abstractmethod +from types import SimpleNamespace + +import pytest +from metaclass_registry import DuplicateRegistryKeyError + +from agent_comms import thread_identity +from agent_comms.thread_identity import ( + CoreWatcherIncarnation, + DeletedThreadIncarnation, + RecordedThreadIncarnation, + ThreadIncarnation, + UnrecordedThreadIncarnation, +) + + +@pytest.mark.parametrize('birth,member,decoded_name', [ + (-2.0, CoreWatcherIncarnation, 'core_watcher'), + (-1.0, DeletedThreadIncarnation, 'original'), + (0.0, UnrecordedThreadIncarnation, 'original'), + (1.0, RecordedThreadIncarnation, 'original'), +]) +def test_external_birth_markers_survive_wire_and_cli_boundaries(birth, member, decoded_name): + wire = {'name': 'original', 'created_at': birth} + identity = ThreadIncarnation.from_wire(wire) + assert type(identity) is member + assert identity.to_wire() == {**wire, 'name': decoded_name} + assert ThreadIncarnation.from_arguments(identity.cli_arguments) == identity + renamed = identity.renamed('renamed') + assert type(renamed) is member + assert renamed.to_wire() == {**wire, 'name': 'renamed'} + + +@pytest.mark.parametrize('birth', [-3.0, float('inf'), float('nan')]) +def test_unknown_nonrecorded_birth_cannot_create_a_live_identity(birth): + with pytest.raises(ValueError): + ThreadIncarnation.from_wire({'name': 'unknown', 'created_at': birth}) + + +@pytest.fixture +def identity_family(): + # Execute the actual class declarations without changing the canonical registry. + return SimpleNamespace(**runpy.run_path(thread_identity.__file__, run_name=thread_identity.__name__)) + + +def test_new_concrete_marker_drives_the_existing_decoder(identity_family): + class HistoricalMarker(identity_family.BirthlessIncarnation): + WIRE_BIRTH = -91.0 + + @property + def birth_description(self) -> str: + return 'historical marker' + + wire = {'name': 'historical', 'created_at': HistoricalMarker.WIRE_BIRTH} + identity = identity_family.ThreadIncarnation.from_wire(wire) + assert type(identity) is HistoricalMarker + assert identity.to_wire() == wire + assert identity_family.ThreadIncarnation.from_arguments(identity.cli_arguments) == identity + with pytest.raises(ValueError, match='positive recorded birth'): + identity.require_recorded() + + +def test_abstract_specialization_does_not_contest_an_existing_marker(identity_family): + class UnfinishedDeleted(identity_family.DeletedThreadIncarnation): + @abstractmethod + def unfinished(self): ... + + with pytest.raises(TypeError, match='abstract'): + UnfinishedDeleted('name') + decoded = identity_family.ThreadIncarnation.from_wire({'name': 'name', 'created_at': -1.0}) + assert type(decoded) is identity_family.DeletedThreadIncarnation + + +def test_new_abstract_marker_is_not_a_decoder(identity_family): + class UnfinishedMarker(identity_family.BirthlessIncarnation): + WIRE_BIRTH = -92.0 + + with pytest.raises(ValueError, match='positive recorded birth'): + identity_family.ThreadIncarnation.from_wire({'name': 'name', 'created_at': UnfinishedMarker.WIRE_BIRTH}) + + +def test_two_concrete_owners_cannot_claim_one_marker(identity_family): + with pytest.raises(DuplicateRegistryKeyError): + class CompetingDeleted(identity_family.DeletedThreadIncarnation): + pass From a785a216bbc3aa1501a066576928e3a728c2095a Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 03:03:48 -0400 Subject: [PATCH 16/17] Derive path evidence and consume the sibling registry typing fix --- pyproject.toml | 3 ++ .../coordination_tables/refusals.py | 32 +++++++++---------- src/agent_comms/declared_family.py | 2 +- src/agent_comms/phase_evidence.py | 25 +++++++++++---- src/agent_comms/plan_phases.py | 30 +++++++++-------- src/agent_comms/thread_text.py | 1 + 6 files changed, 56 insertions(+), 37 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5ee4d2fbb4..8c295a2dc9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -129,3 +129,6 @@ extend-immutable-calls = ["agent_comms.store_files.ExclusiveLock"] warn_return_any = true warn_unused_configs = true disallow_untyped_defs = true + +[tool.uv.sources] +metaclass-registry = { git = "https://github.com/OpenHCSDev/metaclass-registry", branch = "main" } diff --git a/src/agent_comms/coordination_tables/refusals.py b/src/agent_comms/coordination_tables/refusals.py index 9228c98b41..13c239244b 100644 --- a/src/agent_comms/coordination_tables/refusals.py +++ b/src/agent_comms/coordination_tables/refusals.py @@ -44,22 +44,22 @@ def install(cls, store: Coordination) -> None: @classmethod def record(cls, root: Path, sender: str, target: str, error: ToolRefusedError) -> None: - with (guard_original_root_write(root), - MaintenanceBarrier(RegistryFile.path_in(root)).allow_input()): - with ( - CoordinationSession(str(CoordinationStore.path_in(root))) as store, - store.writing() as db, - ): - cls.require_schema(db) - # SQLite allocates this attempt's identity. The text is exactly - # the refusal returned to the caller, not a page's summary. - cls.insert_values( - db, - sender=sender, - target=target, - reason=str(error), - refused_at_ms=time.time_ns() // 1_000_000, - ) + with ( + guard_original_root_write(root), + MaintenanceBarrier(RegistryFile.path_in(root)).allow_input(), + CoordinationSession(str(CoordinationStore.path_in(root))) as store, + store.writing() as db, + ): + cls.require_schema(db) + # SQLite allocates this attempt's identity. The text is exactly + # the refusal returned to the caller, not a page's summary. + cls.insert_values( + db, + sender=sender, + target=target, + reason=str(error), + refused_at_ms=time.time_ns() // 1_000_000, + ) @classmethod def recent( diff --git a/src/agent_comms/declared_family.py b/src/agent_comms/declared_family.py index d033744c11..7bee054fc2 100644 --- a/src/agent_comms/declared_family.py +++ b/src/agent_comms/declared_family.py @@ -7,7 +7,7 @@ from collections.abc import Mapping from typing import Any, ClassVar, Self, cast -from metaclass_registry import ( # type: ignore[import-untyped] +from metaclass_registry import ( AutoRegisterMeta, LazyDiscoveryDict, RegistryConfig, diff --git a/src/agent_comms/phase_evidence.py b/src/agent_comms/phase_evidence.py index 7a03f07032..f20fb439ed 100644 --- a/src/agent_comms/phase_evidence.py +++ b/src/agent_comms/phase_evidence.py @@ -16,6 +16,7 @@ from collections import Counter from collections.abc import Iterable, Iterator from dataclasses import dataclass, field +from pathlib import Path from typing import TYPE_CHECKING, ClassVar from agent_comms.commander_parity import ParityRow @@ -49,8 +50,17 @@ def grep(self, *options: str, pattern: str, paths: tuple[str, ...]) -> list[str] A submodule is another repository's tree, regardless of local recurse settings. Git pathspecs such as ``:!file`` exclude. """ - run = self.git("grep", "--no-recurse-submodules", *options, - "-E", "-e", pattern, self.commit, "--", *paths) + run = self.git( + "grep", + "--no-recurse-submodules", + *options, + "-E", + "-e", + pattern, + self.commit, + "--", + *paths, + ) if run.returncode not in (0, 1): run.check_returncode() return run.stdout.splitlines() @@ -272,6 +282,8 @@ class BusStorageAbsent(MainEvidence, ast.NodeVisitor): symbols = re.compile(r"WireLog|wire_log|_wire_lock_path|has_private_wire_fields") filenames = re.compile(r"bus[.]jsonl|bus_meta[.]json") + lock_name: ClassVar[str] = "wire" + path_methods: ClassVar[tuple[str, ...]] = (Path.with_name.__name__, Path.joinpath.__name__) found = False def read(self, main: MainTree) -> bool: @@ -316,15 +328,14 @@ def visit_Expr(self, node: ast.Expr) -> None: def visit_BinOp(self, node: ast.BinOp) -> None: match node: - case ast.BinOp(op=ast.Div(), right=ast.Constant(value="wire")): - self.found = True + case ast.BinOp(op=ast.Div(), right=ast.Constant(value=value)): + self.found |= value == self.lock_name self.generic_visit(node) def visit_Call(self, node: ast.Call) -> None: match node: - case ast.Call(func=ast.Attribute(attr="with_name" | "joinpath"), - args=[*_, ast.Constant(value="wire")]): - self.found = True + case ast.Call(func=ast.Attribute(attr=method), args=[*_, ast.Constant(value=value)]): + self.found |= method in self.path_methods and value == self.lock_name self.generic_visit(node) diff --git a/src/agent_comms/plan_phases.py b/src/agent_comms/plan_phases.py index 196a5f807f..4f6f1eb1ef 100644 --- a/src/agent_comms/plan_phases.py +++ b/src/agent_comms/plan_phases.py @@ -653,19 +653,23 @@ class KindFamiliesPhase(RemainingSlopPhase): class RostersPhase(RemainingSlopPhase): """6.4: remove the known internal registries; external producer ABI stays independent.""" - evidence = AllEvidence(( - PatternAbsent(r"^\s*THREAD_CAPABILITIES\s*[:=]"), - ClassFlagsAbsent(r"^\s*serves_(new_)?threads\s*[:=]"), - *(ClassFlagsAbsent(r"^\s*" + attribute + r"\s*[:=]", class_names=(owner,)) - for owner, attribute in ( - ("Carry", "declared"), - ("ThreadRename", "kinds"), - ("BirthlessIncarnation", "members"), - ("CliToolArguments", "types"), - ("PiTurnPhase", "_start_phases"), - ("PiTurnPhase", "_end_events"), - )), - )) + evidence = AllEvidence( + ( + PatternAbsent(r"^\s*THREAD_CAPABILITIES\s*[:=]"), + ClassFlagsAbsent(r"^\s*serves_(new_)?threads\s*[:=]"), + *( + ClassFlagsAbsent(r"^\s*" + attribute + r"\s*[:=]", class_names=(owner,)) + for owner, attribute in ( + ("Carry", "declared"), + ("ThreadRename", "kinds"), + ("BirthlessIncarnation", "members"), + ("CliToolArguments", "types"), + ("PiTurnPhase", "_start_phases"), + ("PiTurnPhase", "_end_events"), + ) + ), + ) + ) class RecordsPhase(RemainingSlopPhase): diff --git a/src/agent_comms/thread_text.py b/src/agent_comms/thread_text.py index 7b142c0f6f..c5e3f14b18 100644 --- a/src/agent_comms/thread_text.py +++ b/src/agent_comms/thread_text.py @@ -158,6 +158,7 @@ def applied(self, previous: SessionFile) -> SessionFile: class NoSessionFile(AbsentText, SessionFile): def location(self, backend: type[AgentBackend]) -> RuntimeLocation: from .unstarted_runtime import UnstartedRuntime + return UnstartedRuntime(backend) def applied(self, previous: SessionFile) -> SessionFile: From eca97aa32b23f5283752544cecff6f7b520fa348 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 03:16:20 -0400 Subject: [PATCH 17/17] Bind phase evidence to declarations and separate the import layers --- .github/workflows/debt-ratchet.yml | 5 +- src/agent_comms/check_root.py | 3 +- src/agent_comms/historical_views.py | 3 +- src/agent_comms/phase_evidence.py | 7 +- src/agent_comms/pi_events.py | 22 +- src/agent_comms/pi_turn_phase.py | 33 + src/agent_comms/plan_phases.py | 19 +- src/agent_comms/presentation.py | 3 +- src/agent_comms/registry_provenance.py | 3 +- src/agent_comms/registry_store.py | 2 +- src/agent_comms/session_file.py | 47 + src/agent_comms/thread_identity.py | 59 +- src/agent_comms/thread_management.py | 139 +-- src/agent_comms/thread_provenance.py | 68 ++ src/agent_comms/thread_rename.py | 154 ++++ src/agent_comms/thread_text.py | 37 +- src/agent_comms/threads.py | 5 +- src/agent_comms/tools.py | 3 +- src/agent_comms/transcripts.py | 2 +- src/agent_comms/work_dispatch.py | 2 +- tests/acp_error_payload_installed_journey.py | 2 +- tests/c4_coordination_installed_ui.py | 2 +- tests/guards/test_source_proof_ownership.py | 2 +- tests/reply_relevance_installed_journey.py | 2 +- tests/shared_bus_restart_native.py | 2 +- tests/test_agent_backend.py | 2 +- tests/test_atomic_turn_source_owner.py | 2 +- tests/test_channels.py | 2 +- tests/test_cli.py | 2 +- tests/test_context_source_inspection.py | 2 +- tests/test_declarations.py | 2 +- tests/test_durable_host_records.py | 2 +- tests/test_durable_runtime_location.py | 2 +- tests/test_external_transcript.py | 2 +- tests/test_historical_views.py | 4 +- tests/test_importing.py | 2 +- .../test_native_owner_launcher_resolution.py | 2 +- tests/test_native_unknown_recovery.py | 2 +- tests/test_operations.py | 2 +- tests/test_owner_process.py | 3 +- tests/test_registration_inheritance.py | 2 +- tests/test_registration_process_identity.py | 3 +- tests/test_registration_transitions.py | 2 +- tests/test_restart.py | 2 +- tests/test_retained_context_operations.py | 2 +- tests/test_roster_restoration.py | 2 +- tests/test_runtime.py | 2 +- tests/test_runtime_subscription_handshake.py | 2 +- tests/test_standing_rules.py | 2 +- tests/test_start.py | 2 +- tests/test_task_decisions.py | 2 +- tests/test_thread_identity.py | 2 +- tests/test_thread_status.py | 2 +- tests/test_thread_unread.py | 2 +- tests/test_transcript_input_display.py | 2 +- tests/test_transcript_pages.py | 2 +- tests/test_transcript_partial_tail.py | 2 +- tests/test_transcript_read_identity.py | 2 +- tests/test_transcript_read_lifetime.py | 2 +- tests/test_turn_digests.py | 2 +- tests/test_unit_state.py | 3 +- tests/test_user_view_mark_read.py | 2 +- uv.lock | 829 ++++++++++++++++++ 63 files changed, 1223 insertions(+), 311 deletions(-) create mode 100644 src/agent_comms/pi_turn_phase.py create mode 100644 src/agent_comms/session_file.py create mode 100644 src/agent_comms/thread_provenance.py create mode 100644 src/agent_comms/thread_rename.py create mode 100644 uv.lock diff --git a/.github/workflows/debt-ratchet.yml b/.github/workflows/debt-ratchet.yml index 0e03744890..2db25e2e82 100644 --- a/.github/workflows/debt-ratchet.yml +++ b/.github/workflows/debt-ratchet.yml @@ -138,7 +138,10 @@ jobs: python-version-file: pyproject.toml - name: Install agent-comms - run: python -m pip install -e '.[dev]' + run: | + python -m pip install uv==0.12.0 + uv sync --locked --all-extras + echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH" - name: No new structural debt against the base env: diff --git a/src/agent_comms/check_root.py b/src/agent_comms/check_root.py index 74d2de6d26..6dd7f100c8 100644 --- a/src/agent_comms/check_root.py +++ b/src/agent_comms/check_root.py @@ -25,7 +25,8 @@ from .read_basis import MemberActivityDisplayScope from .registry_document import RosterVisibility from .storage_rules import StorageRoots -from .thread_text import NoThreadText, SavedSessionFile +from .thread_text import NoThreadText +from .session_file import SavedSessionFile from .threads import Thread from .transcript_routes import TranscriptRoutes diff --git a/src/agent_comms/historical_views.py b/src/agent_comms/historical_views.py index a9431107ba..8c175b58cd 100644 --- a/src/agent_comms/historical_views.py +++ b/src/agent_comms/historical_views.py @@ -48,7 +48,8 @@ from .registry_provenance import RegistryProvenance from .revision_views import BusSourceView from .store_files import _atomic_write_text, _store_lock -from .thread_identity import ThreadProvenance, UnrecordedThreadIncarnation +from .thread_identity import UnrecordedThreadIncarnation +from .thread_provenance import ThreadProvenance if TYPE_CHECKING: from .catalog_document import CatalogDocument diff --git a/src/agent_comms/phase_evidence.py b/src/agent_comms/phase_evidence.py index f20fb439ed..ecec1e43ae 100644 --- a/src/agent_comms/phase_evidence.py +++ b/src/agent_comms/phase_evidence.py @@ -575,20 +575,21 @@ class ClassFlagsAbsent(MainEvidence): pattern: str paths: tuple[str, ...] = ("src",) - class_names: tuple[str, ...] = () + owners: tuple[type, ...] = () def read(self, main: MainTree) -> bool: matched = main.matching_lines(self.pattern, self.paths) + class_names = frozenset(owner.__name__ for owner in self.owners) return not any( (path, statement.lineno) in matched for path in {path for path, _ in matched} for declared in Classes.of((main.module(path).tree,)) - if not self.class_names or declared.name in self.class_names + if not class_names or declared.name in class_names for statement in declared.body ) def describe(self) -> str: - owners = f" of {', '.join(self.class_names)}" if self.class_names else "" + owners = f" of {', '.join(owner.__name__ for owner in self.owners)}" if self.owners else "" return f"no class body line {self.pattern!r}{owners} in {' '.join(self.paths)} on main" diff --git a/src/agent_comms/pi_events.py b/src/agent_comms/pi_events.py index f0fdece621..fbe3953b28 100644 --- a/src/agent_comms/pi_events.py +++ b/src/agent_comms/pi_events.py @@ -39,6 +39,7 @@ UnknownPayload, UnreportedUsage, ) +from .pi_turn_phase import PiTurnPhase from .pi_vocabulary import CompactionReason, UnknownCompactionReason from .request_progress import RequestProgress from .tool_declaration import ToolDeclaration @@ -53,27 +54,6 @@ def retry_progress(self) -> bool: return True -class PiTurnPhase: - """Pi declarations own event entry/end matching; shared phases own stage behavior.""" - - _start_phases: ClassVar[dict[type[object], tuple[type[phases.TurnPhase], ...]]] = {} - _end_events: ClassVar[dict[type[object], frozenset[type[object]]]] = {} - - def on(self, current: phases.TurnPhase) -> phases.TurnPhase: - for event_type in type(self).__mro__: - for member in self._start_phases.get(event_type, ()): - return self.enter(member, current) - return self.leave(current) if self.matches_end(current) else current - - def enter(self, member: type[phases.TurnPhase], current: phases.TurnPhase) -> phases.TurnPhase: - return member() - - def matches_end(self, current: phases.TurnPhase) -> bool: - return any(event_type in self._end_events.get(member, frozenset()) - for member in type(current).__mro__ for event_type in type(self).__mro__) - - def leave(self, current: phases.TurnPhase) -> phases.TurnPhase: - return phases.ModelWaitPhase() @dataclass(frozen=True) diff --git a/src/agent_comms/pi_turn_phase.py b/src/agent_comms/pi_turn_phase.py new file mode 100644 index 0000000000..91984b4cf7 --- /dev/null +++ b/src/agent_comms/pi_turn_phase.py @@ -0,0 +1,33 @@ +"""Pi event declarations own their transitions into shared turn phases.""" + +from __future__ import annotations + +from typing import ClassVar + +from . import turn_phase as phases + + +class PiTurnPhase: + """Pi declarations own event entry/end matching; shared phases own stage behavior.""" + + _start_phases: ClassVar[dict[type[object], tuple[type[phases.TurnPhase], ...]]] = {} + _end_events: ClassVar[dict[type[object], frozenset[type[object]]]] = {} + + def on(self, current: phases.TurnPhase) -> phases.TurnPhase: + for event_type in type(self).__mro__: + for member in self._start_phases.get(event_type, ()): + return self.enter(member, current) + return self.leave(current) if self.matches_end(current) else current + + def enter(self, member: type[phases.TurnPhase], current: phases.TurnPhase) -> phases.TurnPhase: + return member() + + def matches_end(self, current: phases.TurnPhase) -> bool: + return any( + event_type in self._end_events.get(member, frozenset()) + for member in type(current).__mro__ + for event_type in type(self).__mro__ + ) + + def leave(self, current: phases.TurnPhase) -> phases.TurnPhase: + return phases.ModelWaitPhase() diff --git a/src/agent_comms/plan_phases.py b/src/agent_comms/plan_phases.py index 4f6f1eb1ef..7e75d53208 100644 --- a/src/agent_comms/plan_phases.py +++ b/src/agent_comms/plan_phases.py @@ -12,6 +12,11 @@ from typing import TYPE_CHECKING, ClassVar from agent_comms.declared_family import DeclaredFamily +from agent_comms.installer.carry import Carry +from agent_comms.pi_turn_phase import PiTurnPhase +from agent_comms.thread_identity import BirthlessIncarnation +from agent_comms.thread_rename import ThreadRename +from agent_comms.tool_calls import CliToolArguments from agent_comms.phase_evidence import ( AllEvidence, BusStorageAbsent, @@ -658,14 +663,14 @@ class RostersPhase(RemainingSlopPhase): PatternAbsent(r"^\s*THREAD_CAPABILITIES\s*[:=]"), ClassFlagsAbsent(r"^\s*serves_(new_)?threads\s*[:=]"), *( - ClassFlagsAbsent(r"^\s*" + attribute + r"\s*[:=]", class_names=(owner,)) + ClassFlagsAbsent(r"^\s*" + attribute + r"\s*[:=]", owners=(owner,)) for owner, attribute in ( - ("Carry", "declared"), - ("ThreadRename", "kinds"), - ("BirthlessIncarnation", "members"), - ("CliToolArguments", "types"), - ("PiTurnPhase", "_start_phases"), - ("PiTurnPhase", "_end_events"), + (Carry, "declared"), + (ThreadRename, "kinds"), + (BirthlessIncarnation, "members"), + (CliToolArguments, "types"), + (PiTurnPhase, "_start_phases"), + (PiTurnPhase, "_end_events"), ) ), ) diff --git a/src/agent_comms/presentation.py b/src/agent_comms/presentation.py index e2d350df85..259db557c1 100644 --- a/src/agent_comms/presentation.py +++ b/src/agent_comms/presentation.py @@ -74,7 +74,8 @@ ) from .runtime_info import RuntimeInfo from .storage_room import RoomLedger -from .thread_identity import ThreadIncarnation, ThreadProvenance +from .thread_identity import ThreadIncarnation +from .thread_provenance import ThreadProvenance from .thread_presentation import ( ThreadOwnerBinding, ThreadPresentation, diff --git a/src/agent_comms/registry_provenance.py b/src/agent_comms/registry_provenance.py index c92cae41af..86a6373275 100644 --- a/src/agent_comms/registry_provenance.py +++ b/src/agent_comms/registry_provenance.py @@ -14,7 +14,8 @@ from .agent_activity import UnavailableRecipientActivity from .errors import RelationViolationError, UnregisteredThreadError from .routing import DeliveryScope -from .thread_identity import ThreadIncarnation, ThreadProvenance +from .thread_identity import ThreadIncarnation +from .thread_provenance import ThreadProvenance if TYPE_CHECKING: from .audience_manifest import FrozenRecipient diff --git a/src/agent_comms/registry_store.py b/src/agent_comms/registry_store.py index 4c12d05743..800a333472 100644 --- a/src/agent_comms/registry_store.py +++ b/src/agent_comms/registry_store.py @@ -32,7 +32,7 @@ from .routing import TurnRouting from .store_files import SharedLock from .thread_identity import TurnIdentity -from .thread_text import SavedSessionFile +from .session_file import SavedSessionFile from .threads import Thread from .turn_lease import ActiveTurn, RecordedGeneration, StoredTurnRecord diff --git a/src/agent_comms/session_file.py b/src/agent_comms/session_file.py new file mode 100644 index 0000000000..94b29c0d35 --- /dev/null +++ b/src/agent_comms/session_file.py @@ -0,0 +1,47 @@ +"""Saved-session representation owns binding to the selected runtime.""" + +from __future__ import annotations + +from abc import abstractmethod +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from .field_codec import SubclassUnion +from .thread_text import AbsentText, GivenText, TextChoice +from .unstarted_runtime import UnstartedRuntime + +if TYPE_CHECKING: + from .agent_backend import AgentBackend, RuntimeLocation + + +class SessionFile(SubclassUnion, TextChoice, affix="SessionFile"): + """A thread's saved native session file, or null before its runtime first saves one.""" + + @abstractmethod + def location(self, backend: type[AgentBackend]) -> RuntimeLocation: + """The runtime this file binds under the thread's backend.""" + + @abstractmethod + def applied(self, previous: SessionFile) -> SessionFile: + """The file a redeclaration keeps: its own, or the previous one.""" + + +@dataclass(frozen=True, slots=True) +class SavedSessionFile(GivenText, SessionFile): + text: str + + def location(self, backend: type[AgentBackend]) -> RuntimeLocation: + return backend.location_type(self.text) + + def applied(self, previous: SessionFile) -> SessionFile: + return self + + +@dataclass(frozen=True, slots=True) +class NoSessionFile(AbsentText, SessionFile): + def location(self, backend: type[AgentBackend]) -> RuntimeLocation: + + return UnstartedRuntime(backend) + + def applied(self, previous: SessionFile) -> SessionFile: + return previous diff --git a/src/agent_comms/thread_identity.py b/src/agent_comms/thread_identity.py index 148c58acbc..3c812b08ea 100644 --- a/src/agent_comms/thread_identity.py +++ b/src/agent_comms/thread_identity.py @@ -10,15 +10,13 @@ import math from abc import ABC, abstractmethod from collections.abc import Iterable -from dataclasses import dataclass, field, fields, replace +from dataclasses import dataclass, field, replace from functools import cached_property from typing import TYPE_CHECKING, Callable, ClassVar, Self, TypeVar, Annotated from metaclass_registry import AutoRegisterMeta, RegistryConfig from agent_comms.participant_identity import ParticipantIdentity -from .agent_backend import AgentBackend, NewThreadBackend, RuntimeLocation -from .channel_targets import Tag from .cli_options import JsonFlagRecord from .declared_family import DeclaredFamily, declared_words from .errors import RelationViolationError @@ -26,11 +24,8 @@ FieldCodec, WireValue, WireName, - WireOmitDefault, - WireRequired, ) from .response_policy import CollectivePolicy, InformationalPolicy, ResponsePolicy -from .thread_text import NoSessionFile, NoThreadText, SessionFile, ThreadText if TYPE_CHECKING: from .child_process import ProcessIdentity @@ -293,58 +288,6 @@ def birth_description(self) -> str: return "Core's watcher" -@dataclass(frozen=True, kw_only=True) -class ThreadProvenance: - """Recorded thread identity, without permission to own a process or start a turn.""" - - name: str - tags: frozenset[str] - worktree: str - created_at: Annotated[float, WireRequired()] - session_file: SessionFile = field(default_factory=NoSessionFile) - title: ThreadText = field(default_factory=NoThreadText) - backend: Annotated[type[AgentBackend], WireOmitDefault()] = field( - default_factory=NewThreadBackend.selected - ) - - @cached_property - def runtime(self) -> RuntimeLocation: - """The codec decoded both stored fields; their record owns this binding.""" - return self.session_file.location(self.backend) - - @property - def display_title(self) -> str: - return self.title.otherwise(self.name) - - @staticmethod - def require_name(name: str) -> None: - allowed = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_") - if not name or not set(name) <= allowed: - raise ValueError(f"Thread name {name!r} must be alphanumeric with hyphens/underscores.") - - def __post_init__(self) -> None: - self.incarnation - self.require_name(self.name) - if not self.worktree: - raise ValueError("Thread worktree cannot be empty.") - for tag in self.tags: - Tag.validate(tag) - - @staticmethod - def capture(thread: ThreadProvenance) -> ThreadProvenance: - """Freeze only determining provenance from the original typed declaration.""" - return ThreadProvenance( - **{ - item.name: getattr(thread, item.name) - for item in fields(ThreadProvenance) - if item.init - } - ) - - @cached_property - def incarnation(self) -> ThreadIncarnation: - """The declaration's birth member, created once and shared by its readers.""" - return ThreadIncarnationRecord(self.name, self.created_at).incarnation() @dataclass(frozen=True, slots=True) diff --git a/src/agent_comms/thread_management.py b/src/agent_comms/thread_management.py index 89d04380aa..7e1d4aec59 100644 --- a/src/agent_comms/thread_management.py +++ b/src/agent_comms/thread_management.py @@ -6,24 +6,21 @@ import os import re import stat -from abc import ABC, abstractmethod +from abc import abstractmethod from collections.abc import Iterator, Sequence from contextlib import contextmanager from dataclasses import dataclass, field, replace from pathlib import Path -from typing import TYPE_CHECKING, ClassVar, Self +from typing import TYPE_CHECKING, Self from uuid import uuid4 -from agent_comms.coordination_contracts import MAX_IDENTIFIER_CHARS -from agent_comms.coordination_errors import CoordinationError, ParticipantNotRegistered +from agent_comms.coordination_errors import CoordinationError from .child_process import NO_PROCESS, ProcessIdentity, ProcessState from .cli_result import CliResult -from .coordination_database import CoordinationStore from .native_entries import ImportedSessionWriter from .registration import Registration from .thread_status import StoppedThreadStatus -from .tool_result import ToolResult if TYPE_CHECKING: from .cli_bridges.history import History @@ -42,19 +39,17 @@ from .owner_lifecycle import LaunchInput, OwnerLifecycle, TaskLaunchInput from .pi_vocabulary import NoThinkingOverride, ThinkingChoice, ThinkingLevelField from .private_registry_guard import ( - PrivateOwnerRenameIntent, - PrivateRegistryGuard, - PublicRegistry, RegistryPrivacy, - _require_no_private_owner_rename, ) from .registry_document import RegistrySnapshot from .registry_provenance import RegistryNames from .store_files import _atomic_write_text from .thread_identity import AgentThreadRole, ThreadRole -from .thread_text import ForkedParent, GivenThreadText, NoThreadText, SavedSessionFile, ThreadText +from .thread_text import ForkedParent, GivenThreadText, NoThreadText, ThreadText +from .session_file import SavedSessionFile from .thread_model import DeclaredModel, ThreadModel, UndeclaredModel from .thread_owned_state import ThreadOwnedState +from .thread_rename import RenameThreadResult, ThreadRename from .threads import KeptModel, ModelChoice, NextTurnConfiguration, Thread, current_thread @@ -204,11 +199,6 @@ class ThreadDeleted(CliResult): detached_children: tuple[str, ...] -@dataclass(frozen=True, slots=True) -class RenameThreadResult(ToolResult): - previous: str - current: str - changed: bool @dataclass(frozen=True, slots=True) @@ -534,120 +524,3 @@ def adopt_current(self) -> Thread: """Declare and register the current process's thread from env.""" thread = current_thread(self.registry) return self.registry.declare(thread) - - -@dataclass(frozen=True, slots=True) -class ThreadRename(ABC): - """One rename, its kind derived from the registry's privacy.""" - - privacy: ClassVar[type[RegistryPrivacy]] - kinds: ClassVar[dict[type[RegistryPrivacy], type[ThreadRename]]] = {} - threads: ThreadManagement - before: Thread - new_name: str - registry_privacy: RegistryPrivacy - - def __init_subclass__(cls, **kwargs): - # slots=True rebuilds the class; its final declaration replaces the first. - super(ThreadRename, cls).__init_subclass__(**kwargs) - cls.kinds[cls.privacy] = cls - - @classmethod - def chosen(cls, threads: ThreadManagement, before: Thread, new_name: str) -> ThreadRename: - privacy = threads._privacy() - return cls.kinds[type(privacy)](threads, before, new_name, privacy) - - @abstractmethod - def renamed(self) -> tuple[str, str]: - """Move the registry name, after whatever this kind's owner stores need first.""" - - def registry_renamed(self) -> tuple[str, str]: - return self.threads.registry.rename(self.before.name, self.new_name) - - def applied(self, title: ThreadText) -> RenameThreadResult: - previous, current = self.renamed() - threads = self.threads - thread = threads.registry.require(current) - titled = replace(thread, auto_title_pending=False, title=title.applied(thread.title)) - if titled != thread: - threads.registry.register(titled, threads.registry.status(current)) - if previous == current: - return RenameThreadResult(previous, current, False) - threads.agents.activity.rename_thread(previous, current) - threads.agents.runtime_info.rename_thread(previous, current) - threads.ledger.rename_thread(previous, current) - with threads.catalog.editing() as document: - document.rename_thread(previous, current) - return RenameThreadResult(previous, current, True) - - -class PlainRename(ThreadRename): - privacy = PublicRegistry - - def renamed(self) -> tuple[str, str]: - return self.registry_renamed() - - -@dataclass(frozen=True, slots=True) -class PrivateOwnerRename(ThreadRename): - """Moves a committed coordinator owner under a durable intent, so a crash between the two - stores fails closed instead of turning an old attempt into a new send.""" - - privacy = PrivateRegistryGuard - registry_privacy: PrivateRegistryGuard - - def renamed(self) -> tuple[str, str]: - from .coordinator import Coordination - - root, registry = self.threads.root, self.threads.registry - before, new_name = self.before, self.new_name - _require_no_private_owner_rename(root) - coordinator = CoordinationStore.path_in(root) - if not coordinator.exists() or before.name == new_name: - return self.registry_renamed() - # Validate registry/SQL name refusals before renaming the private owner. - # Registry readers cannot select a different owner while both stores change. - registry.status(before.name).require_self_rename() - if registry.snapshot().taken(new_name, owner=before.name): - raise RelationViolationError(f"Thread name {new_name!r} is already in use.") - Thread.require_name(new_name) - if len(new_name) > MAX_IDENTIFIER_CHARS: - raise ValueError("Private coordinator owner name exceeds its bound.") - with Coordination(str(coordinator)) as store: - try: - person = store.participants.get(before.incarnation.require_recorded().lookup) - except ParticipantNotRegistered: - return self.registry_renamed() - if not person.committed or person.owner_identity.owner_thread != before.name: - raise RelationViolationError( - "Private coordinator owner differs from the registered owner.") - generation = person.owner_identity.generation - PrivateOwnerRenameIntent(lookup=person.lookup, old=before.name, new=new_name, - generation=generation, - wire_root_id=self.registry_privacy.root_id.hex(), - ).write(root) - store.participants.rename_owner( - person.lookup, new_name, expected_generation=generation) - try: - return self.registry_renamed() - except BaseException as error: - # Registry persistence can fail after the SQL CAS. If its atomic snapshot still - # names the old owner, restore that name without revoking its active attempt. - uncertain = RelationViolationError( - "Private owner rename is uncertain; inspect both authorities.") - try: - if registry.require(before.name).name == before.name: - store.participants.rename_owner( - person.lookup, before.name, expected_generation=generation) - except BaseException as compensation: - # Ambiguous dual-store failure: the inspection needs both failures. - raise uncertain from compensation - raise uncertain from error - - def applied(self, title: ThreadText) -> RenameThreadResult: - result = ThreadRename.applied(self, title) - # The registry lock and renamed() bind a present intent to this rename; - # it completes only after both authorities and the ancillary stores agree. - if PrivateOwnerRenameIntent.path_in(self.threads.root).exists(): - PrivateOwnerRenameIntent.complete(self.threads.root) - return result diff --git a/src/agent_comms/thread_provenance.py b/src/agent_comms/thread_provenance.py new file mode 100644 index 0000000000..a2c21d6f4e --- /dev/null +++ b/src/agent_comms/thread_provenance.py @@ -0,0 +1,68 @@ +"""A recorded thread declaration, independent of live execution authority.""" + +from __future__ import annotations + +from dataclasses import dataclass, field, fields +from functools import cached_property +from typing import Annotated + +from .agent_backend import AgentBackend, NewThreadBackend, RuntimeLocation +from .channel_targets import Tag +from .field_codec import WireOmitDefault, WireRequired +from .thread_identity import ThreadIncarnation, ThreadIncarnationRecord +from .thread_text import NoThreadText, ThreadText +from .session_file import NoSessionFile, SessionFile + + +@dataclass(frozen=True, kw_only=True) +class ThreadProvenance: + """Recorded thread identity, without permission to own a process or start a turn.""" + + name: str + tags: frozenset[str] + worktree: str + created_at: Annotated[float, WireRequired()] + session_file: SessionFile = field(default_factory=NoSessionFile) + title: ThreadText = field(default_factory=NoThreadText) + backend: Annotated[type[AgentBackend], WireOmitDefault()] = field( + default_factory=NewThreadBackend.selected + ) + + @cached_property + def runtime(self) -> RuntimeLocation: + """The codec decoded both stored fields; their record owns this binding.""" + return self.session_file.location(self.backend) + + @property + def display_title(self) -> str: + return self.title.otherwise(self.name) + + @staticmethod + def require_name(name: str) -> None: + allowed = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_") + if not name or not set(name) <= allowed: + raise ValueError(f"Thread name {name!r} must be alphanumeric with hyphens/underscores.") + + def __post_init__(self) -> None: + self.incarnation + self.require_name(self.name) + if not self.worktree: + raise ValueError("Thread worktree cannot be empty.") + for tag in self.tags: + Tag.validate(tag) + + @staticmethod + def capture(thread: ThreadProvenance) -> ThreadProvenance: + """Freeze only determining provenance from the original typed declaration.""" + return ThreadProvenance( + **{ + item.name: getattr(thread, item.name) + for item in fields(ThreadProvenance) + if item.init + } + ) + + @cached_property + def incarnation(self) -> ThreadIncarnation: + """The declaration's birth member, created once and shared by its readers.""" + return ThreadIncarnationRecord(self.name, self.created_at).incarnation() diff --git a/src/agent_comms/thread_rename.py b/src/agent_comms/thread_rename.py new file mode 100644 index 0000000000..b89cbadd96 --- /dev/null +++ b/src/agent_comms/thread_rename.py @@ -0,0 +1,154 @@ +"""The rename operation and its privacy-specific ownership transaction.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from dataclasses import dataclass, replace +from typing import TYPE_CHECKING, ClassVar + +from .coordination_contracts import MAX_IDENTIFIER_CHARS +from .coordination_database import CoordinationStore +from .coordination_errors import ParticipantNotRegistered +from .coordinator import Coordination +from .errors import RelationViolationError +from .private_registry_guard import ( + PrivateOwnerRenameIntent, + PrivateRegistryGuard, + PublicRegistry, + RegistryPrivacy, + _require_no_private_owner_rename, +) +from .thread_provenance import ThreadProvenance +from .thread_text import ThreadText +from .tool_result import ToolResult + +if TYPE_CHECKING: + from .thread_management import ThreadManagement + from .threads import Thread + + +@dataclass(frozen=True, slots=True) +class RenameThreadResult(ToolResult): + previous: str + current: str + changed: bool + + +@dataclass(frozen=True, slots=True) +class ThreadRename(ABC): + """One rename, its kind derived from the registry's privacy.""" + + privacy: ClassVar[type[RegistryPrivacy]] + kinds: ClassVar[dict[type[RegistryPrivacy], type[ThreadRename]]] = {} + threads: ThreadManagement + before: Thread + new_name: str + registry_privacy: RegistryPrivacy + + def __init_subclass__(cls, **kwargs): + # slots=True rebuilds the class; its final declaration replaces the first. + super(ThreadRename, cls).__init_subclass__(**kwargs) + cls.kinds[cls.privacy] = cls + + @classmethod + def chosen(cls, threads: ThreadManagement, before: Thread, new_name: str) -> ThreadRename: + privacy = threads._privacy() + return cls.kinds[type(privacy)](threads, before, new_name, privacy) + + @abstractmethod + def renamed(self) -> tuple[str, str]: + """Move the registry name, after whatever this kind's owner stores need first.""" + + def registry_renamed(self) -> tuple[str, str]: + return self.threads.registry.rename(self.before.name, self.new_name) + + def applied(self, title: ThreadText) -> RenameThreadResult: + previous, current = self.renamed() + threads = self.threads + thread = threads.registry.require(current) + titled = replace(thread, auto_title_pending=False, title=title.applied(thread.title)) + if titled != thread: + threads.registry.register(titled, threads.registry.status(current)) + if previous == current: + return RenameThreadResult(previous, current, False) + threads.agents.activity.rename_thread(previous, current) + threads.agents.runtime_info.rename_thread(previous, current) + threads.ledger.rename_thread(previous, current) + with threads.catalog.editing() as document: + document.rename_thread(previous, current) + return RenameThreadResult(previous, current, True) + + +class PlainRename(ThreadRename): + privacy = PublicRegistry + + def renamed(self) -> tuple[str, str]: + return self.registry_renamed() + + +@dataclass(frozen=True, slots=True) +class PrivateOwnerRename(ThreadRename): + """Moves a committed coordinator owner under a durable intent, so a crash between the two + stores fails closed instead of turning an old attempt into a new send.""" + + privacy = PrivateRegistryGuard + registry_privacy: PrivateRegistryGuard + + def renamed(self) -> tuple[str, str]: + root, registry = self.threads.root, self.threads.registry + before, new_name = self.before, self.new_name + _require_no_private_owner_rename(root) + coordinator = CoordinationStore.path_in(root) + if not coordinator.exists() or before.name == new_name: + return self.registry_renamed() + # Validate registry/SQL name refusals before renaming the private owner. + # Registry readers cannot select a different owner while both stores change. + registry.status(before.name).require_self_rename() + if registry.snapshot().taken(new_name, owner=before.name): + raise RelationViolationError(f"Thread name {new_name!r} is already in use.") + ThreadProvenance.require_name(new_name) + if len(new_name) > MAX_IDENTIFIER_CHARS: + raise ValueError("Private coordinator owner name exceeds its bound.") + with Coordination(str(coordinator)) as store: + try: + person = store.participants.get(before.incarnation.require_recorded().lookup) + except ParticipantNotRegistered: + return self.registry_renamed() + if not person.committed or person.owner_identity.owner_thread != before.name: + raise RelationViolationError( + "Private coordinator owner differs from the registered owner." + ) + generation = person.owner_identity.generation + PrivateOwnerRenameIntent( + lookup=person.lookup, + old=before.name, + new=new_name, + generation=generation, + wire_root_id=self.registry_privacy.root_id.hex(), + ).write(root) + store.participants.rename_owner(person.lookup, new_name, expected_generation=generation) + try: + return self.registry_renamed() + except BaseException as error: + # Registry persistence can fail after the SQL CAS. If its atomic snapshot still + # names the old owner, restore that name without revoking its active attempt. + uncertain = RelationViolationError( + "Private owner rename is uncertain; inspect both authorities." + ) + try: + if registry.require(before.name).name == before.name: + store.participants.rename_owner( + person.lookup, before.name, expected_generation=generation + ) + except BaseException as compensation: + # Ambiguous dual-store failure: the inspection needs both failures. + raise uncertain from compensation + raise uncertain from error + + def applied(self, title: ThreadText) -> RenameThreadResult: + result = ThreadRename.applied(self, title) + # The registry lock and renamed() bind a present intent to this rename; + # it completes only after both authorities and the ancillary stores agree. + if PrivateOwnerRenameIntent.path_in(self.threads.root).exists(): + PrivateOwnerRenameIntent.complete(self.threads.root) + return result diff --git a/src/agent_comms/thread_text.py b/src/agent_comms/thread_text.py index c5e3f14b18..bfc07eb071 100644 --- a/src/agent_comms/thread_text.py +++ b/src/agent_comms/thread_text.py @@ -7,10 +7,11 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, ClassVar + from agent_comms.field_codec import NullMember, SingleFieldValue, SubclassUnion, boundary_decoder if TYPE_CHECKING: - from agent_comms.agent_backend import AgentBackend, RuntimeLocation + pass class TextChoice(ABC): @@ -129,37 +130,3 @@ def names(self) -> tuple[str, ...]: def kept_in(self, names: Collection[str]) -> NoParent: return self - - -class SessionFile(SubclassUnion, TextChoice, affix="SessionFile"): - """A thread's saved native session file, or null before its runtime first saves one.""" - - @abstractmethod - def location(self, backend: type[AgentBackend]) -> RuntimeLocation: - """The runtime this file binds under the thread's backend.""" - - @abstractmethod - def applied(self, previous: SessionFile) -> SessionFile: - """The file a redeclaration keeps: its own, or the previous one.""" - - -@dataclass(frozen=True, slots=True) -class SavedSessionFile(GivenText, SessionFile): - text: str - - def location(self, backend: type[AgentBackend]) -> RuntimeLocation: - return backend.location_type(self.text) - - def applied(self, previous: SessionFile) -> SessionFile: - return self - - -@dataclass(frozen=True, slots=True) -class NoSessionFile(AbsentText, SessionFile): - def location(self, backend: type[AgentBackend]) -> RuntimeLocation: - from .unstarted_runtime import UnstartedRuntime - - return UnstartedRuntime(backend) - - def applied(self, previous: SessionFile) -> SessionFile: - return previous diff --git a/src/agent_comms/threads.py b/src/agent_comms/threads.py index 5ec6894b55..6c4ed7c781 100644 --- a/src/agent_comms/threads.py +++ b/src/agent_comms/threads.py @@ -45,10 +45,11 @@ from .task_sources import TaskScope from .thread_execution import NativeThreadExecution, ThreadExecution from .thread_model import DeclaredModel, ThreadModel, UndeclaredModel -from .thread_text import NoParent, NoSessionFile, NoThreadText, SessionFile, ThreadParent, ThreadText +from .thread_text import NoParent, NoThreadText, ThreadParent, ThreadText +from .session_file import NoSessionFile, SessionFile +from .thread_provenance import ThreadProvenance from .thread_identity import ( AgentThreadRole, - ThreadProvenance, ThreadRole, TurnFence, TurnId, diff --git a/src/agent_comms/tools.py b/src/agent_comms/tools.py index 85ea877ab9..7f055133eb 100644 --- a/src/agent_comms/tools.py +++ b/src/agent_comms/tools.py @@ -156,7 +156,8 @@ ThreadCommandAvailability, ) from .thread_identity import ThreadIncarnation -from .thread_management import InheritedTags, RenameThreadResult, TagChoice +from .thread_management import InheritedTags, TagChoice +from .thread_rename import RenameThreadResult from .thread_model import ThreadModel from .threads import NextTurnConfiguration from .tool_caller import NoThreadCaller, ThreadCaller, ThreadListing, ToolCaller diff --git a/src/agent_comms/transcripts.py b/src/agent_comms/transcripts.py index cbbd6fcc33..37757e260e 100644 --- a/src/agent_comms/transcripts.py +++ b/src/agent_comms/transcripts.py @@ -33,7 +33,7 @@ from .recorded_bus import RecordedBusIdentity from .registration import Registration from .routing import TurnRouting -from .thread_identity import ThreadProvenance +from .thread_provenance import ThreadProvenance from .threads import Thread from .transcript_events import ForkOriginTranscript, ForkTaskTranscript, TranscriptEvent from .transcript_routes import TranscriptRoutes diff --git a/src/agent_comms/work_dispatch.py b/src/agent_comms/work_dispatch.py index a0886dad88..0d0764391e 100644 --- a/src/agent_comms/work_dispatch.py +++ b/src/agent_comms/work_dispatch.py @@ -29,7 +29,7 @@ from .repository_name import RepositoryName from .storage_rules import CheckoutStorageEffect, StorageProjection from .thread_status import StoppedThreadStatus -from .thread_text import SavedSessionFile +from .session_file import SavedSessionFile if TYPE_CHECKING: from .comms import Comms diff --git a/tests/acp_error_payload_installed_journey.py b/tests/acp_error_payload_installed_journey.py index 9671afebd7..a49c4d2f49 100644 --- a/tests/acp_error_payload_installed_journey.py +++ b/tests/acp_error_payload_installed_journey.py @@ -8,7 +8,7 @@ from agent_comms.bus import Bus -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.pi_vocabulary import OffThinkingLevel import argparse import asyncio diff --git a/tests/c4_coordination_installed_ui.py b/tests/c4_coordination_installed_ui.py index b0cc670dba..11e90e1666 100644 --- a/tests/c4_coordination_installed_ui.py +++ b/tests/c4_coordination_installed_ui.py @@ -2,7 +2,7 @@ from agent_comms.bus import Bus from conftest import publish_initial -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.channel_targets import CommandTarget from agent_comms.read_basis import NamedViewer from agent_comms.presentation import ReadThrough diff --git a/tests/guards/test_source_proof_ownership.py b/tests/guards/test_source_proof_ownership.py index d01a502d4b..4b899cbdf0 100644 --- a/tests/guards/test_source_proof_ownership.py +++ b/tests/guards/test_source_proof_ownership.py @@ -79,7 +79,7 @@ def test_archived_identity_cannot_reacquire_a_live_registry_or_thread(): from dataclasses import MISSING, fields from typing import get_type_hints from agent_comms.historical_views import HistorySource, HistoricalThread - from agent_comms.thread_identity import ThreadProvenance + from agent_comms.thread_provenance import ThreadProvenance provenance = next(item for item in fields(HistorySource) if item.name == "provenance") assert provenance.default is MISSING and provenance.default_factory is MISSING diff --git a/tests/reply_relevance_installed_journey.py b/tests/reply_relevance_installed_journey.py index 4aeb705556..c321730d24 100644 --- a/tests/reply_relevance_installed_journey.py +++ b/tests/reply_relevance_installed_journey.py @@ -2,7 +2,7 @@ from bus_record_fixture import prepare_native_comms from agent_comms.lifecycle_capability import Complete -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.native_entries import NativeEvidenceScope import asyncio import hashlib diff --git a/tests/shared_bus_restart_native.py b/tests/shared_bus_restart_native.py index 7dadb915df..37b338c4cc 100644 --- a/tests/shared_bus_restart_native.py +++ b/tests/shared_bus_restart_native.py @@ -9,7 +9,7 @@ from bus_record_fixture import prepare_native_comms from agent_comms.lifecycle_capability import Complete from agent_comms.coordination_session import NativeBoundary -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.native_entries import NativeEvidenceScope from conftest import publish_initial from agent_comms.pi_vocabulary import OffThinkingLevel diff --git a/tests/test_agent_backend.py b/tests/test_agent_backend.py index 6bec560790..2f32efdff2 100644 --- a/tests/test_agent_backend.py +++ b/tests/test_agent_backend.py @@ -19,7 +19,7 @@ from agent_comms.threads import Thread from agent_comms.unstarted_runtime import UnstartedRuntime from agent_comms.thread_model import DeclaredModel -from agent_comms.thread_text import NoSessionFile, SavedSessionFile +from agent_comms.session_file import NoSessionFile, SavedSessionFile # -- registry --------------------------------------------------------------- diff --git a/tests/test_atomic_turn_source_owner.py b/tests/test_atomic_turn_source_owner.py index 3efbff2114..a2f635fd89 100644 --- a/tests/test_atomic_turn_source_owner.py +++ b/tests/test_atomic_turn_source_owner.py @@ -11,7 +11,7 @@ from agent_comms.comms import Comms from conftest import installed_comms from agent_comms.coordination_errors import StaleFence -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.goal_actions import SetGoalAction from agent_comms.native_input_owner import RegistryOwner from agent_comms.owned_turn import OwnedTurn diff --git a/tests/test_channels.py b/tests/test_channels.py index 0bb6dba4c0..5a441b5b99 100644 --- a/tests/test_channels.py +++ b/tests/test_channels.py @@ -1,4 +1,4 @@ -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.pi_vocabulary import HighThinkingLevel import os from dataclasses import replace diff --git a/tests/test_cli.py b/tests/test_cli.py index dfeaa11f66..41addabfda 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -8,7 +8,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.channel_targets import CommandTarget, PerTargetChannels from agent_comms.cli import main from agent_comms.threads import ChosenServiceTier, NextTurnConfiguration diff --git a/tests/test_context_source_inspection.py b/tests/test_context_source_inspection.py index 4cc6481751..24b547366f 100644 --- a/tests/test_context_source_inspection.py +++ b/tests/test_context_source_inspection.py @@ -8,7 +8,7 @@ import pytest -from agent_comms.thread_text import NoSessionFile +from agent_comms.session_file import NoSessionFile from agent_comms.field_codec import FieldCodec from agent_comms.native_session_reopen import NativeSessionIdentity from agent_comms.native_turn_context import NativeContextData, NativeContextManifestData diff --git a/tests/test_declarations.py b/tests/test_declarations.py index 8b28d476a5..b66d299e4a 100644 --- a/tests/test_declarations.py +++ b/tests/test_declarations.py @@ -11,7 +11,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.agent_events import AgentInfo, ContextUsageObservation from agent_comms.child_process import ProcessIdentity from agent_comms.comms import Comms diff --git a/tests/test_durable_host_records.py b/tests/test_durable_host_records.py index c8d54e9942..9038352d27 100644 --- a/tests/test_durable_host_records.py +++ b/tests/test_durable_host_records.py @@ -3,7 +3,7 @@ import json import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.pi_payloads import ReportedText, RequestId from agent_comms.field_codec import FieldCodec, MalformedRecord from agent_comms.native_entries import NativeEntry, StoredEntry, TranscriptProjection diff --git a/tests/test_durable_runtime_location.py b/tests/test_durable_runtime_location.py index d67fedd706..13eebcdcc1 100644 --- a/tests/test_durable_runtime_location.py +++ b/tests/test_durable_runtime_location.py @@ -5,7 +5,7 @@ import pytest -from agent_comms.thread_text import NoSessionFile, SavedSessionFile +from agent_comms.session_file import NoSessionFile, SavedSessionFile from agent_comms.agent_backend import NewThreadBackend from agent_comms.field_codec import FieldCodec from agent_comms.turn_digests import LastTurns diff --git a/tests/test_external_transcript.py b/tests/test_external_transcript.py index 20d2ba7230..da0161a25e 100644 --- a/tests/test_external_transcript.py +++ b/tests/test_external_transcript.py @@ -3,7 +3,7 @@ import pytest from agent_comms.bus import Bus -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.comms import wire from agent_comms.field_codec import FieldCodec from agent_comms.transcript_events import AssistantTranscript, NoticeTranscript diff --git a/tests/test_historical_views.py b/tests/test_historical_views.py index c7b6121e7a..b7156d66ed 100644 --- a/tests/test_historical_views.py +++ b/tests/test_historical_views.py @@ -27,7 +27,7 @@ ) from agent_comms.read_ledger import ReadLedger from agent_comms.routing import MessageRoute, TurnRouting -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile @pytest.mark.parametrize('exists', [False, True]) @@ -358,7 +358,7 @@ def test_historical_namespace_has_recorded_provenance_and_no_live_authority(migr from agent_comms.field_codec import FieldCodec from agent_comms.registry_provenance import RegistryProvenance - from agent_comms.thread_identity import ThreadProvenance + from agent_comms.thread_provenance import ThreadProvenance old, _, live, source = migrated recorded = source.provenance.require("alice") diff --git a/tests/test_importing.py b/tests/test_importing.py index 15ab505965..bd8a135dcd 100644 --- a/tests/test_importing.py +++ b/tests/test_importing.py @@ -26,7 +26,7 @@ from agent_comms.harness_install import ClaudeCodeHarness, CodexHarness, OpenCodeHarness from agent_comms.importing import ImportBuffer, ThreadImported, ImportSnapshot, ImportedSessionMetadata from agent_comms.native_entries import NativeEntry, ImportedMetadataEntry -from agent_comms.thread_identity import ThreadProvenance +from agent_comms.thread_provenance import ThreadProvenance from agent_comms.thread_status import StoppedThreadStatus from agent_comms.runtime_updates import FinishedItem from agent_comms.transcript_events import ImportedReply, ImportedRequest, TextTranscript diff --git a/tests/test_native_owner_launcher_resolution.py b/tests/test_native_owner_launcher_resolution.py index 246a0ab5f6..44ef5aa6ba 100644 --- a/tests/test_native_owner_launcher_resolution.py +++ b/tests/test_native_owner_launcher_resolution.py @@ -6,7 +6,7 @@ from pathlib import Path -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms import native_package from agent_comms.launch_root import LaunchRoot from agent_comms.launch_runtime import LaunchRuntime diff --git a/tests/test_native_unknown_recovery.py b/tests/test_native_unknown_recovery.py index 9c8c4bc4b3..694c45c7c7 100644 --- a/tests/test_native_unknown_recovery.py +++ b/tests/test_native_unknown_recovery.py @@ -23,7 +23,7 @@ from agent_comms.launch_root import LaunchRoot from agent_comms.runtime import socket_path from agent_comms.thread_model import DeclaredModel -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from functools import partial from native_backend_fixture import native_backend_fixture from agent_comms.child_process import ProcessIdentity diff --git a/tests/test_operations.py b/tests/test_operations.py index f7d43198b8..600b3a11ac 100644 --- a/tests/test_operations.py +++ b/tests/test_operations.py @@ -8,7 +8,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.native_entries import CompactionEntry, ModelChangeEntry from agent_comms.channel_targets import ChannelTarget, CommandTarget, ThreadTarget from agent_comms.channels import AnyChannel diff --git a/tests/test_owner_process.py b/tests/test_owner_process.py index 00f7cc9955..6f990359a2 100644 --- a/tests/test_owner_process.py +++ b/tests/test_owner_process.py @@ -28,7 +28,8 @@ from agent_comms.native_arguments import GivenArguments, GivenBinary from agent_comms.restart_scope import OwnersNamed from agent_comms.thread_model import DeclaredModel -from agent_comms.thread_text import GivenThreadText, NoSessionFile +from agent_comms.thread_text import GivenThreadText +from agent_comms.session_file import NoSessionFile from delivery_owner_fixture import canonical_agent from native_backend_fixture import native_backend_fixture diff --git a/tests/test_registration_inheritance.py b/tests/test_registration_inheritance.py index 7b64164952..c9d582c599 100644 --- a/tests/test_registration_inheritance.py +++ b/tests/test_registration_inheritance.py @@ -3,7 +3,7 @@ from typing import Annotated from dataclasses import dataclass, replace -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms import field_codec from agent_comms.comms import wire from agent_comms.pi_vocabulary import HighThinkingLevel diff --git a/tests/test_registration_process_identity.py b/tests/test_registration_process_identity.py index 83f28137aa..35add0cd5a 100644 --- a/tests/test_registration_process_identity.py +++ b/tests/test_registration_process_identity.py @@ -15,7 +15,8 @@ InstallingPhase, MaintenanceOperator, PausedPhase, ReadyPhase, ) from agent_comms.thread_status import NoBridgeThreadStatus -from agent_comms.thread_text import GivenThreadText, SavedSessionFile +from agent_comms.thread_text import GivenThreadText +from agent_comms.session_file import SavedSessionFile @pytest.mark.parametrize('phases', [(), (PausedPhase,), (PausedPhase, InstallingPhase)]) diff --git a/tests/test_registration_transitions.py b/tests/test_registration_transitions.py index e2d3976630..186f90f359 100644 --- a/tests/test_registration_transitions.py +++ b/tests/test_registration_transitions.py @@ -5,7 +5,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.child_process import ProcessIdentity from agent_comms.errors import RelationViolationError from agent_comms.registration import Registration diff --git a/tests/test_restart.py b/tests/test_restart.py index 9f0542dff2..ec7d6dc678 100644 --- a/tests/test_restart.py +++ b/tests/test_restart.py @@ -11,7 +11,7 @@ from agent_comms.native_arguments import GivenBinary from agent_comms.restart_scope import OwnersNamed from agent_comms.thread_model import DeclaredModel -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from native_backend_fixture import native_backend_fixture diff --git a/tests/test_retained_context_operations.py b/tests/test_retained_context_operations.py index e91ef2139d..852fca2b54 100644 --- a/tests/test_retained_context_operations.py +++ b/tests/test_retained_context_operations.py @@ -8,7 +8,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.cli import main from agent_comms.cli_commands import ConstraintPinnedResult from agent_comms.field_codec import FieldCodec diff --git a/tests/test_roster_restoration.py b/tests/test_roster_restoration.py index 96c179095a..7ff2e69c59 100644 --- a/tests/test_roster_restoration.py +++ b/tests/test_roster_restoration.py @@ -9,7 +9,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.coordination_tables.executions import NoPointedExecution from agent_comms.thread_identity import RecordedThreadIncarnation from agent_comms.child_process import NoProcess, ProcessIdentity diff --git a/tests/test_runtime.py b/tests/test_runtime.py index 5710fdf9aa..6ac8127d63 100644 --- a/tests/test_runtime.py +++ b/tests/test_runtime.py @@ -23,7 +23,7 @@ ) from agent_comms.private_path import PrivateSocketRole from agent_comms.thread_identity import RecordedThreadIncarnation -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.native_arguments import GivenBinary from delivery_owner_fixture import canonical_agent diff --git a/tests/test_runtime_subscription_handshake.py b/tests/test_runtime_subscription_handshake.py index 79b33444f4..380018b0a1 100644 --- a/tests/test_runtime_subscription_handshake.py +++ b/tests/test_runtime_subscription_handshake.py @@ -8,7 +8,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.acp import CommsAgent from agent_comms.child_process import ProcessIdentity from agent_comms.comms import wire diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index 94fe330093..9d67036eb5 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -3,7 +3,7 @@ from dataclasses import replace -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.todos import (DirectiveText) diff --git a/tests/test_start.py b/tests/test_start.py index ecbc7f5caa..3b929bf10a 100644 --- a/tests/test_start.py +++ b/tests/test_start.py @@ -5,7 +5,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.tools import invoke_tool from agent_comms.comms import wire from agent_comms.tool_caller import NoThreadCaller diff --git a/tests/test_task_decisions.py b/tests/test_task_decisions.py index 8cd0b49153..feeab15a4e 100644 --- a/tests/test_task_decisions.py +++ b/tests/test_task_decisions.py @@ -4,7 +4,7 @@ from agent_comms.bus import Bus -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.bus_publication import AgentOrigin from agent_comms.messages import NoticeRole import os diff --git a/tests/test_thread_identity.py b/tests/test_thread_identity.py index 1a70b57156..7bc6a58f8a 100644 --- a/tests/test_thread_identity.py +++ b/tests/test_thread_identity.py @@ -6,7 +6,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.child_process import NoProcess, ProcessIdentity from agent_comms.comms import Comms from conftest import installed_comms diff --git a/tests/test_thread_status.py b/tests/test_thread_status.py index a46ad8301b..5e7e1e188a 100644 --- a/tests/test_thread_status.py +++ b/tests/test_thread_status.py @@ -8,7 +8,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.activity import Activity, ActivityState from agent_comms.comms import wire from conftest import installed_comms diff --git a/tests/test_thread_unread.py b/tests/test_thread_unread.py index ff9913a85f..54442e3375 100644 --- a/tests/test_thread_unread.py +++ b/tests/test_thread_unread.py @@ -10,7 +10,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.comms import wire from conftest import installed_comms from agent_comms.private_path import FileRevision diff --git a/tests/test_transcript_input_display.py b/tests/test_transcript_input_display.py index 25b364c02a..aa6be40033 100644 --- a/tests/test_transcript_input_display.py +++ b/tests/test_transcript_input_display.py @@ -6,7 +6,7 @@ import pytest from conftest import transcript_records -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.acp import CommsAgent from agent_comms.acp_extension import TranscriptSnapshotUpdate, decode_updates from agent_comms.comms import wire diff --git a/tests/test_transcript_pages.py b/tests/test_transcript_pages.py index 9129a6f5a1..cf936064dc 100644 --- a/tests/test_transcript_pages.py +++ b/tests/test_transcript_pages.py @@ -1,5 +1,5 @@ from conftest import transcript_records -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.coordination_errors import TranscriptChanged import json from dataclasses import replace diff --git a/tests/test_transcript_partial_tail.py b/tests/test_transcript_partial_tail.py index 0ddfab7b81..7c7a073c12 100644 --- a/tests/test_transcript_partial_tail.py +++ b/tests/test_transcript_partial_tail.py @@ -3,7 +3,7 @@ from agent_comms.private_runtime_schema import PrivateCoordinatorTable import json -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.comms import wire from agent_comms.coordinator import Coordination diff --git a/tests/test_transcript_read_identity.py b/tests/test_transcript_read_identity.py index 16678477d7..c4ff0b8e7d 100644 --- a/tests/test_transcript_read_identity.py +++ b/tests/test_transcript_read_identity.py @@ -6,7 +6,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from conftest import installed_comms from agent_comms.coordination_errors import StaleRevision from agent_comms.thread_text import ForkedParent, GivenThreadText diff --git a/tests/test_transcript_read_lifetime.py b/tests/test_transcript_read_lifetime.py index 8dde6d9968..51a31a369f 100644 --- a/tests/test_transcript_read_lifetime.py +++ b/tests/test_transcript_read_lifetime.py @@ -10,7 +10,7 @@ import pytest -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.view_unread import transcript_read_state from agent_comms.comms import wire from native_unread_fixture import native_reader diff --git a/tests/test_turn_digests.py b/tests/test_turn_digests.py index bb9e119d3e..2b471760ac 100644 --- a/tests/test_turn_digests.py +++ b/tests/test_turn_digests.py @@ -19,7 +19,7 @@ from agent_comms.field_codec import FieldCodec from agent_comms.runtime_updates import FinishedItem from agent_comms.thread_execution import ExternalThreadExecution -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.transcript_events import AssistantTranscript, TurnDigest from agent_comms.turn_digests import LastTurns, TurnBound, TurnsOlderThan, TurnsBefore, TurnsSince from agent_comms.bus import Bus diff --git a/tests/test_unit_state.py b/tests/test_unit_state.py index 0890a43f8e..12171a2d4a 100644 --- a/tests/test_unit_state.py +++ b/tests/test_unit_state.py @@ -14,7 +14,8 @@ from agent_comms.read_basis import DMDisplayScope from agent_comms.recorded_time import RecordedEventTime from agent_comms.thread_status import StoppedThreadStatus -from agent_comms.thread_text import ForkedParent, SavedSessionFile +from agent_comms.thread_text import ForkedParent +from agent_comms.session_file import SavedSessionFile from agent_comms.todos import DeliverTodo, OperationDelivery, ProgressRead from agent_comms.ui_model.unit_state import ( AwaitingCommander, diff --git a/tests/test_user_view_mark_read.py b/tests/test_user_view_mark_read.py index c64e1507c5..bc6658f2e1 100644 --- a/tests/test_user_view_mark_read.py +++ b/tests/test_user_view_mark_read.py @@ -1,6 +1,6 @@ """Explicit human Mark read never acknowledges a persistent agent's inbox.""" -from agent_comms.thread_text import SavedSessionFile +from agent_comms.session_file import SavedSessionFile from agent_comms.channel_targets import CommandTarget from agent_comms.read_basis import NamedViewer import json diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000000..ac9a137874 --- /dev/null +++ b/uv.lock @@ -0,0 +1,829 @@ +version = 1 +revision = 3 +requires-python = "==3.14.*" + +[[package]] +name = "agent-client-protocol" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/95/2c28ca34e545ce0282e88272aa3a8abdf8f3f926a0492f567ea11acd071b/agent_client_protocol-0.12.1.tar.gz", hash = "sha256:9963ccb590ed96d7dc542c9c023e90676fc76b1c6562e21a9ee112f4fbcb0095", size = 130605, upload-time = "2026-08-16T14:08:22.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/61/0a2186c8ca53cf464767d4c4fb7fd9e23f48ce4547c2fa0d1c83a4d52410/agent_client_protocol-0.12.1-py3-none-any.whl", hash = "sha256:aaf3cc301ed87d9e2c4ccb91adfe1cd58784355f6ce7e513f50e7ca2391862ef", size = 85929, upload-time = "2026-08-16T14:08:21.307Z" }, +] + +[[package]] +name = "agent-comms" +source = { editable = "." } +dependencies = [ + { name = "agent-client-protocol" }, + { name = "aiohttp" }, + { name = "metaclass-registry" }, + { name = "packaging" }, + { name = "pydantic" }, +] + +[package.optional-dependencies] +dev = [ + { name = "black" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-timeout" }, + { name = "pytest-xdist" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "agent-client-protocol", specifier = ">=0.8" }, + { name = "aiohttp", specifier = ">=3.14" }, + { name = "black", marker = "extra == 'dev'", specifier = "==26.5.1" }, + { name = "metaclass-registry", git = "https://github.com/OpenHCSDev/metaclass-registry?branch=main" }, + { name = "mypy", marker = "extra == 'dev'", specifier = "==2.3.0" }, + { name = "packaging", specifier = ">=24" }, + { name = "pydantic", specifier = ">=2.13" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" }, + { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.0" }, + { name = "pytest-timeout", marker = "extra == 'dev'", specifier = ">=2.3" }, + { name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.6" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.16.0" }, +] +provides-extras = ["dev"] + +[[package]] +name = "aiohappyeyeballs" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757, upload-time = "2026-07-01T17:11:55.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038, upload-time = "2026-07-01T17:11:54.055Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.14.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/4c/bdccd81e9ee225b69c60e7766c9a5b05364f118f4d383713b89a682d772d/aiohttp-3.14.5.tar.gz", hash = "sha256:5558a7f5a05af9ecf744af91e5baefc436f93c9333e656c27ec253f9a6bbe178", size = 8078052, upload-time = "2026-10-11T01:05:12.408Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/be/3184a1d34a8be665569eadb7e9e764b4629e4f3413e241cb2e4d6fecf3b3/aiohttp-3.14.5-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:939042d5cda21d41a6f512e7cc8b8e33a2aebff863352251da495fbd91b673b5", size = 540673, upload-time = "2026-10-11T01:01:41.354Z" }, + { url = "https://files.pythonhosted.org/packages/60/2a/d35f3ba4cf157b072e3b674bf9983047ca5ea5173c995d32d877e1191d36/aiohttp-3.14.5-cp314-cp314-android_24_x86_64.whl", hash = "sha256:6da32b5ff3fd78d244e37300463434c7145162bfd2b6e9e915ab164da37f7343", size = 554087, upload-time = "2026-10-11T01:01:43.719Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d2/61a33880ca4eaca95a9c60ca3f6beed15555af1028652dfaac601627787b/aiohttp-3.14.5-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1b438b73c38111818d0c9d6a5c2bfed8584c8e503a49ef085d70e874ec846738", size = 522613, upload-time = "2026-10-11T01:01:46.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/de579b299d2225dc2c6fd99d579d91f16c02a913fb5af9a3cf2fe9bd88ba/aiohttp-3.14.5-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:755933b107ea7a6a9ac916f635a70595a5b1a32fac10a8ff0b9f2ab88555550c", size = 529682, upload-time = "2026-10-11T01:01:48.477Z" }, + { url = "https://files.pythonhosted.org/packages/f4/4a/ddb923564e15e053b6e060b0036e1694dcadcb13aa476c5a87dcad20e336/aiohttp-3.14.5-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:b3cc509327c7b27f6f4727a8830f4004f6df7766e179f2f4b8e54e65c0bec5d3", size = 540120, upload-time = "2026-10-11T01:01:50.474Z" }, + { url = "https://files.pythonhosted.org/packages/3d/36/a640fbecaa53727a5900b892bdbe17b5f3e8cc88903e22864fe41b654def/aiohttp-3.14.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7bd8ac754ebd6733a3e2a0dd1674c4d8ab086196803fd8dcd776f07b4e2607d9", size = 818795, upload-time = "2026-10-11T01:01:52.665Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b6/d52ca608859e271b5fa7944074802dc45f60e52c318a4ddc34edbf73586e/aiohttp-3.14.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e724a7b6091f0b1ac064f9d1b15ff9ec52e6033a86cdae649e5f086e32a3c0db", size = 549667, upload-time = "2026-10-11T01:01:54.65Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b5/05b8ac39a76ff4bca89f42a4c2471560c71f894ff6e4bc16158c951874e3/aiohttp-3.14.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c32e26310cc10e547f53cd13d39a369034f69dcb7d749d5cb0e5f67bc196b6ba", size = 548922, upload-time = "2026-10-11T01:01:56.547Z" }, + { url = "https://files.pythonhosted.org/packages/19/b0/5aa186d56ce2334dabe29b70bd99dc8ae926ee44184c0de64a53d415a4f3/aiohttp-3.14.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1eb8167961ec4dfcc8cb9dd50bd0ee72519f7ef496be95203e49e27b01618382", size = 1882422, upload-time = "2026-10-11T01:01:59.258Z" }, + { url = "https://files.pythonhosted.org/packages/db/f7/7d5c91bb9620db300c8ddb05337a9014301acb626223faff3abcb8ea47d7/aiohttp-3.14.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c1d60eafd9c7e8e74abd03a5b00df44e7febfe6d9b89b559c0a6551eef0699d4", size = 1843621, upload-time = "2026-10-11T01:02:01.417Z" }, + { url = "https://files.pythonhosted.org/packages/30/0a/b208953b96d8f24b75f6da704f508e6c5cf3022f52b60c61933df082e89c/aiohttp-3.14.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:137351bf20bbed9a65e839f4a4452ac377389bdb2f2857d2acffef38f5e9f2d1", size = 1927915, upload-time = "2026-10-11T01:02:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/f6/79/90ebcccb55e2d1e11a1fed581d83bb966e38fb35fb4b2577fdc980f8707a/aiohttp-3.14.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fba47bc2c3d7303c3d027c6cf4d07626c37b1314ac81f5820c31032e0ca1f677", size = 2036339, upload-time = "2026-10-11T01:02:06.046Z" }, + { url = "https://files.pythonhosted.org/packages/a6/66/55a8904b3a129fafdf94f9cc0a2e4ca09a3c914650be52355db7ad0bbdb6/aiohttp-3.14.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94684b879ac1d71e4238850c99b62dc1b28d9086b156a2555f082010b85a865c", size = 1888041, upload-time = "2026-10-11T01:02:08.384Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b8/96b25da7329a52e42c812b1e8b076386039ec4fc312afa043d173d8147fc/aiohttp-3.14.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56572c42e3ecd636de8d2c3dd54cf5fc939cb5c32eb56297f176a0d366fac622", size = 1703849, upload-time = "2026-10-11T01:02:10.903Z" }, + { url = "https://files.pythonhosted.org/packages/1b/43/fbf976e3ae4c038d6f5c84945ab2150298c2d71201674d4e53d158063e75/aiohttp-3.14.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a95529a92a446db351675f4aab518feaf5e99842f63f5dd17160c2b74f382db3", size = 1849703, upload-time = "2026-10-11T01:02:13.15Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7d/218e912f4c1d89bde7ac551409be57ad2f6121638e56942d395a7cb1fa58/aiohttp-3.14.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:3edbece0379b8b4aaa67619b8aa2399bb66fce372cd5911098a434ea77220aa0", size = 1852695, upload-time = "2026-10-11T01:02:15.295Z" }, + { url = "https://files.pythonhosted.org/packages/5a/42/252a1b9287e3b6e393a1c3bd1776f36af30f5f25f071bbf2b0cb7eba9116/aiohttp-3.14.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:56d9828f204331a5ca8850fcfe2bcce95a149f1f223f60cc7216e5524978e480", size = 1893493, upload-time = "2026-10-11T01:02:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/78/97/71cae83d5100556fad1521684f7cd1e3e578432644f850245ed3bd969310/aiohttp-3.14.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:20064a177a070d789ee64a50b01a9161d3468e989baacfc6c714aa685c4b332f", size = 1693468, upload-time = "2026-10-11T01:02:20.666Z" }, + { url = "https://files.pythonhosted.org/packages/1f/69/73d88e97a8b5f0ca7a946d0011c0de99fb188b1687c7948ecd0553dc5bf0/aiohttp-3.14.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:81c2b3dfd56c62bee6108e4852d5970b4cf9086390b6983f52b666e878c1f115", size = 1915398, upload-time = "2026-10-11T01:02:23.011Z" }, + { url = "https://files.pythonhosted.org/packages/05/f0/881644bcb15d4b258daea9b720a0af9dc4330496cc8d6ade9090cdd0cffc/aiohttp-3.14.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:09ec102b4b8c9a920275733bbc11fdbb615efe6f9231a06007c0218d336fb77a", size = 1871470, upload-time = "2026-10-11T01:02:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/7f/de/19d9ebbcce5aedaa3242d8a99ff8816a60bdb7629fb0084bf4a45bfd1f62/aiohttp-3.14.5-cp314-cp314-win32.whl", hash = "sha256:9c428eb2bd8817588d16a0ab898aa4eb5d141f896aa2b394cc79a4cf61d9a8e2", size = 485517, upload-time = "2026-10-11T01:02:27.579Z" }, + { url = "https://files.pythonhosted.org/packages/a9/74/8cdaf0e58c2588371670d5a9a8215bbb971d36940b6e5d051967dd05c07d/aiohttp-3.14.5-cp314-cp314-win_amd64.whl", hash = "sha256:6f967dde489ca6a8c02d093ab245d2cbf50ccb5c36adf0188b17b0ca39d24b67", size = 511755, upload-time = "2026-10-11T01:02:29.685Z" }, + { url = "https://files.pythonhosted.org/packages/1a/6b/e0100e25502430a531c7cf1482a378d0b65bf728ab60c01ee270e56bc469/aiohttp-3.14.5-cp314-cp314-win_arm64.whl", hash = "sha256:1d2d981b53dd09a319e3570ef8cc3bbc3ef86f5a7abef0f6b2bff3867db3a9e7", size = 495151, upload-time = "2026-10-11T01:02:32.163Z" }, + { url = "https://files.pythonhosted.org/packages/9d/c2/ca2ead7b655688c53c03aeeb6e96e6851c9ff08d6be7b13802f53a6ae8fd/aiohttp-3.14.5-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9ce66feae6ac65327379460380549bf1b8df8e17c4e25df2a2bcf168272e3bed", size = 855396, upload-time = "2026-10-11T01:02:34.443Z" }, + { url = "https://files.pythonhosted.org/packages/a7/70/22206fea409255a240c926ce11de48de354ae2bb90ca44f709c05497585d/aiohttp-3.14.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:27c2322e03f66101acb09869ce1cf1efc04994ee95e1735b69827bf8c8b9d781", size = 567734, upload-time = "2026-10-11T01:02:36.644Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e5/79a36c118308b56f8667d67e05d2fb6dc638ab45985704cdb199631bedaa/aiohttp-3.14.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff75a7537413a86e7cafe98e0e1d6e3dc4b15c6349896e7d5c6b881bfdb6d550", size = 567379, upload-time = "2026-10-11T01:02:38.757Z" }, + { url = "https://files.pythonhosted.org/packages/63/a3/2ebec7dece3b1f02c30d2e484647f6f7b13952b1bb40a4cb285b849e8432/aiohttp-3.14.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c061aa954daaf57d2a4b8374f9fca621ef0e1b603584431c220c22458c59b6d", size = 2050686, upload-time = "2026-10-11T01:02:41.169Z" }, + { url = "https://files.pythonhosted.org/packages/3a/d2/7e4d093db2f4450482652e7ef19a9e19919028f5135aa52bc4078c3beb80/aiohttp-3.14.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1612fa5857b37bf32e5c1eaeefb96e3b01e9c70679eec81f0934e8a600080863", size = 1915077, upload-time = "2026-10-11T01:02:43.563Z" }, + { url = "https://files.pythonhosted.org/packages/a6/88/bd40d09958442a0a1df67da81de496361d6e2afc04f9db2950d835da750b/aiohttp-3.14.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:adbeee7d6fd4cf5fe0aece2fb3edc4243615d3180430ba8149d01a90670cac99", size = 2027204, upload-time = "2026-10-11T01:02:46.185Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/3a601c1f8d3103c1a60ea981f20924855da9f575d2007fc38f8898b792fb/aiohttp-3.14.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b2966998927d7bed9db12c0a4647b0c7b179755878fc9c357fe1ffd3e3b0c1a5", size = 2149520, upload-time = "2026-10-11T01:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/22/d0/4e41bfe1b1ce1cb6f6d2e59fa7a88ef5cf92c402b2d07e9018778ba9edbc/aiohttp-3.14.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e317e0fb6b16212c881d2205a7d87414c29acd69320b3aa6dce9d9c7b86fe4f", size = 2002039, upload-time = "2026-10-11T01:02:51.293Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/72067019545c502b881b031153c437752ecef48d7d213bc0218ccebb4bfb/aiohttp-3.14.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50343c1757b4b6f6708eeaf24534b32f19dfb99fb1b762c00420867a62fc81e0", size = 1786164, upload-time = "2026-10-11T01:02:53.533Z" }, + { url = "https://files.pythonhosted.org/packages/d9/fe/7741efd6119bfb7a00827fe6f7b84b4409de58d888ae21adc5a9a6824992/aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:083673c7a94c3ea035caaa5ca04288bdb44887abfe1f5ba23294e6a4b03efd2d", size = 1970689, upload-time = "2026-10-11T01:02:55.888Z" }, + { url = "https://files.pythonhosted.org/packages/43/e7/342a13bf67f34d269bf2f7e870ecd72b99c832cc6f0a271a9210c0ebfb84/aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2528cb4c6b92008c76ac9ac6298624069bb2db91ff4929905512d1d84485f658", size = 1951967, upload-time = "2026-10-11T01:02:58.467Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d4/fdb3b27340617e5e64df18a70fa89097778652ea5c7c7e2f79def76999c3/aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b1b8ece1e71132d2afba4dbc0c3d62c766e25165990b25db1196c04969eb3d84", size = 1968511, upload-time = "2026-10-11T01:03:00.883Z" }, + { url = "https://files.pythonhosted.org/packages/83/b2/e8f88298de78d1a951f36f9f966d38ec6ed1d4721303ba02064a545e8aa6/aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fce9523df31cea6284f3e2c479876750d7687cf671d7b25d32b19effc0e86441", size = 1778497, upload-time = "2026-10-11T01:03:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/22/68/9ccdb93d664345c546be7f34480b921774d8c0d98f47e70d7e03b115d475/aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:09e0eb18c7e0c8777e2f9149de63799195b9b3ca1b5c81ba6f32f2c6b8628210", size = 1999676, upload-time = "2026-10-11T01:03:05.829Z" }, + { url = "https://files.pythonhosted.org/packages/57/4a/a33cfa6dcb00e94194ae4fe710432ca4ed111e016356b4ba4d2f4c3a124c/aiohttp-3.14.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6774814fd5c338e72ee0da5cbb9432816df450e69c019f72b5d29bdec2a1792d", size = 1963437, upload-time = "2026-10-11T01:03:08.196Z" }, + { url = "https://files.pythonhosted.org/packages/f4/20/eacbecfea3b5c3dcbfc9b023e3a5460f43e5dda16d06a2877ebe7184c3f3/aiohttp-3.14.5-cp314-cp314t-win32.whl", hash = "sha256:33f706574e32c6e694f352a856e05caf18f7f2c871b3e87b41c55ea452b409ab", size = 503973, upload-time = "2026-10-11T01:03:10.481Z" }, + { url = "https://files.pythonhosted.org/packages/ff/78/18eec294f6c8c5dc845dcf6d730a0147d8d0f17e86138a7bdb85e43a30fa/aiohttp-3.14.5-cp314-cp314t-win_amd64.whl", hash = "sha256:5ba14a839fbe87cf7c12a6b5661c05f324a296eb8363141edb3944ba63d4c9d3", size = 531190, upload-time = "2026-10-11T01:03:12.716Z" }, + { url = "https://files.pythonhosted.org/packages/9d/39/e53f8169acc85271ebd12b5b32ad7f1541b35639ccbe0f49034c64785d10/aiohttp-3.14.5-cp314-cp314t-win_arm64.whl", hash = "sha256:1061b364556e8172e8d46b0b183adeeb73e8c42d30ebc745591e1bd89acad52e", size = 510360, upload-time = "2026-10-11T01:03:15.08Z" }, + { url = "https://files.pythonhosted.org/packages/68/30/173960c42b05a6c59f7558e4b12a4b0d9ba376cf6aa9bde7f9e08a30ca8d/aiohttp-3.14.5-py3-none-any.whl", hash = "sha256:efc21a454892828368b11c2c780de0ff8bc991f73f6b99c6b66e56205470929b", size = 279517, upload-time = "2026-10-11T01:05:08.523Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + +[[package]] +name = "ast-serialize" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/d5/5dc1ee8a80c3b20a0b63c61b43ba6bc53517faecd1def47bcd752d22268e/ast_serialize-0.13.1.tar.gz", hash = "sha256:401cc84e8888d4d76f2c8d296fa006ed1a5ff993b6aff90dd777c03973efd025", size = 955137, upload-time = "2026-10-17T00:04:17.183Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/a8/fbd136b9f70c59e75f59a8e974c84102976f1201828b4f886480ba956936/ast_serialize-0.13.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:3e129d44b0cffbea24a7f40117ce49e30993c3d119e584d4817ebd6f87246456", size = 897306, upload-time = "2026-10-17T00:02:52.479Z" }, + { url = "https://files.pythonhosted.org/packages/50/3f/2ad51e9823a790e7a70fd781e9595ab3abeace27f3aefcaa1939457efd72/ast_serialize-0.13.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:523d45bab16ddaf1399c04c4fe81bbf180f81e33c2f7bcbe76be56b0f7311ab4", size = 1236082, upload-time = "2026-10-17T00:02:54.82Z" }, + { url = "https://files.pythonhosted.org/packages/c0/5c/8e959db5cc2237f9d4c560a8e2253c98f9746883fb4774651e4cda458901/ast_serialize-0.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:638333fb8ae72c62753f9e794fda20290883ddc65b0d85e26e08f8bb227567ad", size = 1221242, upload-time = "2026-10-17T00:02:56.395Z" }, + { url = "https://files.pythonhosted.org/packages/d6/05/ab1c56c3b7adcac88764bba609502d6b4989e449b2dd60eb478e1d20e369/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc34c670747d85d4232834665e247009413eaebe47c69eaf60a5c439aaecaed9", size = 1282105, upload-time = "2026-10-17T00:02:58.275Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f4/41981fcd2ddffe0760e5326be9dcbd2eda9478a8430a637e5123bd8fcb26/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c8ebd419811c435812d242fb2a173e57dfede198c9ac9a0a584666ebc9b9331", size = 1287905, upload-time = "2026-10-17T00:03:00.068Z" }, + { url = "https://files.pythonhosted.org/packages/6a/43/b783addc5681153707121991b7b47892f7e9c8ba58a0d03b71a3b023ba47/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8899c038b1c15ae8b98e379057fb3ef398ceb0b21d1737a8156dd5a0923b7e51", size = 1541806, upload-time = "2026-10-17T00:03:01.503Z" }, + { url = "https://files.pythonhosted.org/packages/47/10/969db095038ee15270f65ad20d1129c5e4da605bf7e93b70ae640f1c8a9f/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e14c4de98f965647a74e6eaf37e20207124e45451ccd36e84a6f6ebab45e0372", size = 1309946, upload-time = "2026-10-17T00:03:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/06/dd/7c623d2fb0b868948952d1787e629a43b866f7d4caefd5445e20aefacec4/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3e8a3eff69af640db985360fef35073fb46956dfc3e5c614cf00920d2746dd", size = 1302773, upload-time = "2026-10-17T00:03:04.378Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c2/4d49469110a19a44fc6269f4fd59679bcf84df068f185f89b73bee4da19b/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:75d7750fe98f6f35a96c43aba8d98a2e623e22c778a0c17ab9ede92eb10f5d82", size = 1299579, upload-time = "2026-10-17T00:03:05.88Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9a/2d381f1f262dda15affc8ad643fba1619eb07e7d9f8bd904deffef183006/ast_serialize-0.13.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e8ddd63e80fc280c2f790157386f27f9b9f6ccb9c4f8e803b863cbfb085769", size = 1355969, upload-time = "2026-10-17T00:03:07.321Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9c/2266794e6c25fa9ce51ce04a9bf4b7e330e3ac9351ba3fa14e487678acdb/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9221e09496fb942901993c1812ee87950cc0ecbb514c7c0f1f5fa6d78f701ed8", size = 1460227, upload-time = "2026-10-17T00:03:08.733Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d6/14deb28e5d8f328f626a0778ea2cf98fe908f2bbcccd804cb6f449ee1782/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:7a5c38bdb93669c596f94fb7204dd6ebe4fadb19058ac9a2faf2948c2a175f96", size = 1562691, upload-time = "2026-10-17T00:03:10.25Z" }, + { url = "https://files.pythonhosted.org/packages/f5/19/7a28c0e5ba54f0d3e054f847a2a549b748cb179ab433321a8dc1b39e398d/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:acf4e9f055d75058ab01536da330648746f2f82b7697a85230547f2d5ebe895c", size = 1558669, upload-time = "2026-10-17T00:03:11.799Z" }, + { url = "https://files.pythonhosted.org/packages/18/9a/f9e2690eab58217f1d395a964a78efa7eaf813a1e38be8dfd9a8d45d0840/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87427913c325adc811e81e7c9a757b061f47ef1e29114ac479aa0953a2687a98", size = 1665071, upload-time = "2026-10-17T00:03:13.284Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f3/ef5b159a0d7161b404f05e7ec21cf7735ee7edc10c000cd2cbbc7bb6b69a/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:ad88921f95b92bc144d9ad059a0e16f8c1ae69f5239f2d3c6536fd2774f73dfe", size = 1473364, upload-time = "2026-10-17T00:03:14.563Z" }, + { url = "https://files.pythonhosted.org/packages/1d/87/6e8ac4a865124a12cc4f915bf1bcfac3bba426cd5f58d3712a873ee61012/ast_serialize-0.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f8b17e2921e56621bcb46792bd3e0b82ca3b85de78d5936e7b8174ba1758b0aa", size = 1500566, upload-time = "2026-10-17T00:03:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1e/29155f1621433008013ad5c940a03d41a676be0680c41527040472d37583/ast_serialize-0.13.1-cp314-cp314t-win32.whl", hash = "sha256:2f9a9ce8b0994f757b941f7f7fe384a6666b4162b941ebc5c248081baea18e47", size = 1128674, upload-time = "2026-10-17T00:03:17.481Z" }, + { url = "https://files.pythonhosted.org/packages/bb/64/2506da65143ea138de5822e9173e0b72320a2f70e23bd723f044033175f1/ast_serialize-0.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5d99910e6b9a9b9360bcca2ae99aee36cc912ce058df2a57d1c33099b6cb2593", size = 1163306, upload-time = "2026-10-17T00:03:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/6d/8f/c7cb22638899b0cf15908809c270560e46395e65604bc59e4b623ab9f405/ast_serialize-0.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:a789cbe14d0a0e9ec9b32a456e5abc81ad8799479c3cfd88a29e3442262aebe6", size = 1133775, upload-time = "2026-10-17T00:03:20.377Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e2/b0c140fe50fa39dd249e382f7733af6f816c90021fe7959a0b026f86237f/ast_serialize-0.13.1-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:23d768369b89d551077dc173da6892fad00443338417523b6732f4e8ba4a7ed5", size = 1236160, upload-time = "2026-10-17T00:03:21.945Z" }, + { url = "https://files.pythonhosted.org/packages/23/c4/a8a2ca21e3943555b48221f8fba546877a7ad39efaf6505506524967d7c4/ast_serialize-0.13.1-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:68f8829d114dab6c9db4a8bbb8d0e342958faee1b5e15a29022678df639960f8", size = 1222214, upload-time = "2026-10-17T00:03:23.462Z" }, + { url = "https://files.pythonhosted.org/packages/15/19/2f58f2ba64e4799b39de85c140aef09a03aa6ea53be75dd292a9d9b9d8ae/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e385b17d5bde892a6184003a9aff3518387908b73f8f5ea539313c07c8c0ede", size = 1282979, upload-time = "2026-10-17T00:03:24.695Z" }, + { url = "https://files.pythonhosted.org/packages/b1/af/ca6087fe84210c1ccd43e3182917bb70649fa1e69745a90c1d59ba08fea8/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:02686b281ec9b8e9614f6f0db27e12e4cac515f336c5d05b678bc9eb73ff2af5", size = 1288596, upload-time = "2026-10-17T00:03:26.04Z" }, + { url = "https://files.pythonhosted.org/packages/b0/2c/4ef964c763329a0347125e745ec0451ba4fbaceec9efe2ac729b7cd55f17/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8ab2c2585b2e22e48cd4aff2136fce4eab4ced569b85f150ef9a5a5d6f34d49", size = 1545540, upload-time = "2026-10-17T00:03:27.52Z" }, + { url = "https://files.pythonhosted.org/packages/bb/85/aa961dbb75813cfa94de0ce9a16a9510c226b95f604c405421e727f44bb0/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c652d9f5d52d8b81eff3ff2c95c9dd0f281de45f6ad5d3fa95527550c396454a", size = 1309374, upload-time = "2026-10-17T00:03:29.091Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e6/f2b67c327d9f9533e41791804403ccc2dc457821195a2e51f0b587a90b90/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121e1d08d58b75c15491cc3003609cbad71ac2b0016043c36d30a6b2032b6605", size = 1303466, upload-time = "2026-10-17T00:03:30.451Z" }, + { url = "https://files.pythonhosted.org/packages/73/1d/33f18b81d1bb1f5d682943ea92fda8130a6593627f12f6201668d40824fd/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:30cf89b6a6844542d116c758725d13de5b97b9410a51ee577715f761270d0c1c", size = 1300576, upload-time = "2026-10-17T00:03:32.102Z" }, + { url = "https://files.pythonhosted.org/packages/25/cb/64914144925f1a4abdd9217e717b122255c38fc5ebe4342f92052f3bacbc/ast_serialize-0.13.1-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:732aaae9e498c973fc63fd7f308009b57c4afa6ff59d0b674d894e57d0bca01a", size = 1355960, upload-time = "2026-10-17T00:03:33.676Z" }, + { url = "https://files.pythonhosted.org/packages/ee/80/bd29379fab2bce19adc0bf9177002b11b23dfe8d8fecf409998b458b4624/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:d9c1a920b2aa0c1e016bed7721689717034ca0dba64ac3a3ff243b003d4e8dd8", size = 1459779, upload-time = "2026-10-17T00:03:35.09Z" }, + { url = "https://files.pythonhosted.org/packages/83/89/012dbe733de05ffdf8e40ca0f9fa5d417cb16ec93a74463c57c001914198/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:847cad4c22953823f5e76d0b126c6f9da670cfa45b1df41b9324e22189d77611", size = 1563399, upload-time = "2026-10-17T00:03:37.285Z" }, + { url = "https://files.pythonhosted.org/packages/69/37/ee4bcb0358bee38744eb0f18560446572d095fe3bf6b63d6f739d08a839c/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:aca07de3ca8e3d9f2b716c3c31f4f384fe4bd2d03e09912417cff7a3e08036f7", size = 1558808, upload-time = "2026-10-17T00:03:38.711Z" }, + { url = "https://files.pythonhosted.org/packages/54/1c/ff44c5355251d61ef22934c4430bb31a61de4781d25a992154aac9b8c82a/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:5db12d843d931b9d1f1519ce66b9cbfe72d8563b75c1204d255322254d1964dd", size = 1666324, upload-time = "2026-10-17T00:03:40.376Z" }, + { url = "https://files.pythonhosted.org/packages/c8/bb/6da4bfc8aa3403e6447607131bf3463acaf954d990c6c8b6787c1902ddfe/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:fb8021f4dc3296de18737e3b8b34994411da725374d74a0b9eb32a738f2a23d0", size = 1474089, upload-time = "2026-10-17T00:03:41.715Z" }, + { url = "https://files.pythonhosted.org/packages/21/97/897e33bef2eefd880e6350e375cc66bc5bcd00a139be480fd655f5b3f095/ast_serialize-0.13.1-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:0d764913374ba21df8cd618045b371ed104ce55c6276de7c59e554a4f5ba26fd", size = 1501542, upload-time = "2026-10-17T00:03:43.08Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4c/821211ac83870c27e4412381b7c7838336456b40575bf840e4680b2018d9/ast_serialize-0.13.1-cp315-abi3.abi3t-win32.whl", hash = "sha256:2bad302ae2427454ef9dab71bd5c3ad67188860c39d36daee38f72031edb39c4", size = 1126118, upload-time = "2026-10-17T00:03:44.616Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/2b2d5e6c4fa352874a80618e742ffd32d249dee6c119f4233b51afc6b8f4/ast_serialize-0.13.1-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:032b09d86c785c3984e975378b7861f2a68ac44e22ba7b07b93a536b2524eeb8", size = 1163766, upload-time = "2026-10-17T00:03:45.986Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/d610cfcdabaa44fd62a54ca1fc57a7dba120643798b089a14914973562de/ast_serialize-0.13.1-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:929eda0c419e622ab49df3a4c45f81c57dcc10874bbad55d0470a4a03324dac3", size = 1134043, upload-time = "2026-10-17T00:03:48.453Z" }, + { url = "https://files.pythonhosted.org/packages/81/92/d705be88ff452b9ba0088b554a0cbd308fd01879da092b33fd2ad7d1aa60/ast_serialize-0.13.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:4bcf1d5f4eefecaa325a34ecf079b2741189e7d0821523f92cb343a389a67489", size = 1242416, upload-time = "2026-10-17T00:03:51.521Z" }, + { url = "https://files.pythonhosted.org/packages/60/c2/98370845a7f35cb8a25782e78284b48e01bf3128339881d48d613f304f8f/ast_serialize-0.13.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1b158f1c9e3a0d386913924c57995d6df78224868a4d0f215bdd41587c97b34a", size = 1229729, upload-time = "2026-10-17T00:03:52.825Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8b/2d7ffed76addd9d3fcf37fb171400942735c8912264c763561a85407f6a8/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a6201b69ce42e919f2fd9a559138d5410b5d8afff7c3a6b1f500b0481ff7d8", size = 1292255, upload-time = "2026-10-17T00:03:54.148Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2a/bb9e0e719d89cc950fadfcfcc9cb4bc7640419c4228c852f031ab8abf440/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eb0be64c9b8f1ed26637eef345abe75ea90351c92c85a8aec7838b3af971011", size = 1298501, upload-time = "2026-10-17T00:03:55.559Z" }, + { url = "https://files.pythonhosted.org/packages/7a/32/13ce07dce48226f5bd9cfd8f708ec101a8be05aa430cda4918ce31ce8c82/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f2427bddfca208e84de700044fadf9aef2769a70bae740a1bdabb89ee9e1117", size = 1553758, upload-time = "2026-10-17T00:03:56.952Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fb/9b51d186db0c20ca28cbaaefd50f03b573c89327b9a2627820cbce22196b/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88df960e67184a71874e5b2ee014f87d1254a0dbc1f903c7bd7b1ab7733ecaef", size = 1318258, upload-time = "2026-10-17T00:03:58.418Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fa/da311559dd3dae9af5825cf21660775a929b970f72e3786af3f2cd667944/ast_serialize-0.13.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f99786e0d62082a5b121b3437f3167562133711df22128de1e9886f57f0b226", size = 1313260, upload-time = "2026-10-17T00:04:00.194Z" }, + { url = "https://files.pythonhosted.org/packages/2b/11/97a4b9e5b4776d72f47fcd9a09a497bbf68ec8c1ae074307a2c51e410d85/ast_serialize-0.13.1-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:a01b515b25a03184eff4acef25b981ae007916379682e908fc8785b342a0bf3e", size = 1309992, upload-time = "2026-10-17T00:04:01.575Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c4/62f85121a80cd451ef04f550cf0f7ea0ad9ff46cc4e9a5b9ae439b6527b1/ast_serialize-0.13.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:29422d7bcd9e7f39411571c36cec45ce8a7a9b3ad4b2e7aa64497c15a9e397f6", size = 1362715, upload-time = "2026-10-17T00:04:02.881Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/c5f4a545f72a7681e3295bab6e3600ee0862ec4391bed961fe9f735b67e0/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ffb0f548a7dc126067fdfb7b2325ab5dfcdeecc29faf5e0f0461c96d6ebfa3d", size = 1469931, upload-time = "2026-10-17T00:04:04.181Z" }, + { url = "https://files.pythonhosted.org/packages/38/a9/65aed4f8fa7a001a7cfdc03e68e844517ab041ba6fb614b7d78a1d6ac056/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:2876444a5f87a7c76f371af3c07c2fda563a08bc8af52568df812e8aba4bf568", size = 1570945, upload-time = "2026-10-17T00:04:05.778Z" }, + { url = "https://files.pythonhosted.org/packages/fb/8c/44b2da991cede79bfbf52089f33a5f0168945466b3d9cd38b5dcf08e7130/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:885a62ec6481ce58ab24c6357360d7a82023b62dd7e5e7ceccf59e9662a3272b", size = 1569978, upload-time = "2026-10-17T00:04:07.347Z" }, + { url = "https://files.pythonhosted.org/packages/46/e4/109b2a5beca700237767d5add920c2f8e1ba9bd5799b0faf143d0e9cf4ef/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:2015fd0165e463aac3b1aadc6a1fa1e5820c14ca950821f4fe02f8a3f6a388d6", size = 1676082, upload-time = "2026-10-17T00:04:08.7Z" }, + { url = "https://files.pythonhosted.org/packages/73/e0/7df70f6dbb11b5762da5416318f08316a1c51348e0d69ec79ca5f98f836a/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:eb41e1906502ac80d24ff343f45008552a3f5b5158607959fe6a07dbfc6eb037", size = 1483207, upload-time = "2026-10-17T00:04:10.162Z" }, + { url = "https://files.pythonhosted.org/packages/1f/bc/523aa1c08bd69b5654621beb3dc1a9035715a4a63762fb8d219d6fb3eb81/ast_serialize-0.13.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ca9acc67f796aa2554fa3037dccc022a4b08b38b2ad153edfac9c9bb7cb1f3c6", size = 1512140, upload-time = "2026-10-17T00:04:11.589Z" }, + { url = "https://files.pythonhosted.org/packages/66/83/6607060659998f00afdea0d8375c1e6e2b7d515ab905090d9a5a02f48851/ast_serialize-0.13.1-cp39-abi3-win32.whl", hash = "sha256:4f98da562e5901d69c7dd30885594c5438f859edf354e9ac7517d188fb026c3a", size = 1131889, upload-time = "2026-10-17T00:04:12.953Z" }, + { url = "https://files.pythonhosted.org/packages/9f/6f/bafd6490fe7b355627fabe95f4417e3d02da93102c8552550eb9a0774e1e/ast_serialize-0.13.1-cp39-abi3-win_amd64.whl", hash = "sha256:ddf7cbc9c8abf70102c4a088efe154bb86bfb825b0a10e556d2b15972cf99bd7", size = 1171153, upload-time = "2026-10-17T00:04:14.362Z" }, + { url = "https://files.pythonhosted.org/packages/11/25/4235f4b375a67a37ac595bf478a5407a744eaf7b64113c1c3a5432b8d954/ast_serialize-0.13.1-cp39-abi3-win_arm64.whl", hash = "sha256:48f11d96c9ec71119612f8e79a8c55c12f2b136716d68347433c561d27a25f43", size = 1139035, upload-time = "2026-10-17T00:04:15.767Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "black" +version = "26.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/16/a8da8eb208c51c7f4ce74609a45d0dcc6d8a2141e45e81ee5289d1bb0d59/black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", size = 2004800, upload-time = "2026-05-18T17:05:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/11/8a/a479296a19e383b70a725882a6cf3d786540601ff03cabbaaf1cce864c5a/black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", size = 1815576, upload-time = "2026-05-18T17:05:40.309Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/cfaf3d39f25132c156a068f6b805576c9103a84086019507c70e1911ee7d/black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", size = 1877927, upload-time = "2026-05-18T17:05:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/66/76/302e313964bcff7e28df329d39f84f5270095730d85ff0acc260610a0d82/black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", size = 1511860, upload-time = "2026-05-18T17:05:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/27/4e/a3827e35e0e567f9f9ee59e2a0ab979267dca98718f25547ca8c6733afd4/black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae", size = 1316632, upload-time = "2026-05-18T17:05:45.521Z" }, + { url = "https://files.pythonhosted.org/packages/94/51/f975cae76d44274cc2868dc9040ac5d58d464784610234455b4e7b19c6ef/black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", size = 213693, upload-time = "2026-05-18T16:53:33.964Z" }, +] + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "coverage" +version = "7.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/79/e78bd8f75a9b8e32233ce92d4607f0a21ce88a9156cace2884e7175a82f5/coverage-7.17.0.tar.gz", hash = "sha256:cc010a0a7fbdd9bb8bdfd0d3f04a5151275860213cbd7e2b876b29778489c712", size = 991021, upload-time = "2026-10-22T16:06:50.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/e1/722462fe79912ebebc101497bd16c42413cde2dbae771017f1ec0acb84c2/coverage-7.17.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:dc5936491e306f2f6f095bd5fba593de11eb0375cee787ca1f245c3a333a523a", size = 227197, upload-time = "2026-10-22T16:05:09.846Z" }, + { url = "https://files.pythonhosted.org/packages/b7/74/826b9c09f2ebfea803bccb391d935688a25309bff8e819fc446bc629c108/coverage-7.17.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:968c5fb6e0c83cab752d79a70209648ce4d90cae815c60fa229b59e264601062", size = 227314, upload-time = "2026-10-22T16:05:11.422Z" }, + { url = "https://files.pythonhosted.org/packages/6d/19/c8514d7f5e0659a96ff2bc7377119dca6d420e2761c5fc69ae37618734f8/coverage-7.17.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:12cb8a20ad904a5811e6660d829a586b216e5254b9b34b76dde8a4d65d827dc6", size = 225477, upload-time = "2026-10-22T16:05:12.761Z" }, + { url = "https://files.pythonhosted.org/packages/81/74/ef9433ce599f07d8633b4ce6e6d6921505c4ef3f7f9006f8eb66e2c278cf/coverage-7.17.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:bb5ca66937c55bc85d1a5e9c5900a3e4693a8edf2280a9b3672b7b2661a84b95", size = 225787, upload-time = "2026-10-22T16:05:14.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/26e58981940e8b3944c6a9941bec5c7f50840e7009a046712e3f3bcd9e6a/coverage-7.17.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5cc15faaf318ee5040d835a62c75bc6f122d837274f3b5efd47bf9fbd4ac5b85", size = 226019, upload-time = "2026-10-22T16:05:15.798Z" }, + { url = "https://files.pythonhosted.org/packages/e9/63/fe175310815cc8357b83b773c9b4c20943c269f217dd825a37c4b4bfe324/coverage-7.17.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7d6d9d396c8b7bcaf76d774a10140b218111484e54c73a5f2a1b75655381748e", size = 226169, upload-time = "2026-10-22T16:05:17.188Z" }, + { url = "https://files.pythonhosted.org/packages/2c/b0/ac84761f5b4bd226696b0ec4606903d2ef7208abda0aec06c14a4218def4/coverage-7.17.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:42e6f170d1ac04b7eb3363310a6c41cdfd82c1fe6bf9ffbc4a86f380bdf091c7", size = 258092, upload-time = "2026-10-22T16:05:18.691Z" }, + { url = "https://files.pythonhosted.org/packages/e9/58/d8bdd65a661913bc12eaaa3e59742b2cb8b94f2e7f78e62184f0a58ca493/coverage-7.17.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:acdb5f3c96046efff9dc6c572a6acb2b8d0da09372d7b9a9ed7249cf7d17434f", size = 261096, upload-time = "2026-10-22T16:05:20.089Z" }, + { url = "https://files.pythonhosted.org/packages/7b/15/5d82e2f0a75ea149e0a6412e137e1d610538772fdfd7199737db7fe234da/coverage-7.17.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3393209f6894713161ee82a9b0e673e58237e0f9733c6638e7a6c0c620ef0b33", size = 262080, upload-time = "2026-10-22T16:05:21.568Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cc/e3ebfd295d1dd47c7bc84a17d73f150465a37ab388dd68fe82b495dc79a0/coverage-7.17.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9494d9e99389d39e6a96227749af898d8924ff591cb63944f8a98e68582bf0e", size = 264558, upload-time = "2026-10-22T16:05:22.906Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9d/e2adff76d6544bbc6df7366d74967923c2de37ba889a006bfb875bf5857b/coverage-7.17.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:032d4c2db4c677eb71d43f2f8b80bf64e9346f7a3f7b6ebb13f1782f3cc6c274", size = 257855, upload-time = "2026-10-22T16:05:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/d9/fd/ecd1e9cddf1c36b5d03c438568b1f53e63db84bb3656539d71b454484c7d/coverage-7.17.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4343a9378b964eb3cb80fa90406385de91c7e982a769ca9314d848f93bc0d3f2", size = 259933, upload-time = "2026-10-22T16:05:25.79Z" }, + { url = "https://files.pythonhosted.org/packages/40/b6/e17a19dccdbcad494c64691f71c1dba3e55c30625bc29ffbb88ac5a0aa8b/coverage-7.17.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:b609df3d6d900464207532c5170a65c6ede8d2e0b91f151f90008a866395cd76", size = 257972, upload-time = "2026-10-22T16:05:27.185Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0d/37dac534709a3fa43a937c8b0604839bb44b8670b83d143a4b78fec20d1e/coverage-7.17.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a01ef92caeedfa4f2ad171a3a8e6fece4565de871d8bca428151bf9929f41048", size = 262271, upload-time = "2026-10-22T16:05:28.619Z" }, + { url = "https://files.pythonhosted.org/packages/cb/17/7c05186925b4d64a46c271dfd944e3f0c1ad89db9095c31a6df157a75df2/coverage-7.17.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a3914e2c98c34b94c26d2add3d2a61ccfd3534fc4eaa879ee290bdbb5184ba6c", size = 257604, upload-time = "2026-10-22T16:05:30.07Z" }, + { url = "https://files.pythonhosted.org/packages/95/65/c241cdee66a0ff86c68f6d37328e735d10e3d30cd6ff66f356039eb688d2/coverage-7.17.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8a72b26dc5c9a9422e45472b1d67ba5caf273c524c8668cc04b23062663755e", size = 259714, upload-time = "2026-10-22T16:05:31.402Z" }, + { url = "https://files.pythonhosted.org/packages/db/09/e93233fe2405574ce0a31ed201c0cba3a860e085db5d2828bf12b7dc0790/coverage-7.17.0-cp314-cp314-win32.whl", hash = "sha256:03baf0688e14a5a44ffd1eefa8928dfc92b14565e521174e072b31b971aa1657", size = 227914, upload-time = "2026-10-22T16:05:32.78Z" }, + { url = "https://files.pythonhosted.org/packages/d7/77/04979eb217edb94b94afabb4bcc179f60c746f68d3fd0b12688ba9195730/coverage-7.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:3a4459b0f389dd1e51aac3dbfc4ca35b30cae7dc99caa3c0a086d1997a985709", size = 228543, upload-time = "2026-10-22T16:05:34.118Z" }, + { url = "https://files.pythonhosted.org/packages/a0/38/155f2f2c6769f8c43eca8a5e581443faa0908828650090b97103b4a41376/coverage-7.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:a32ee5831b15ba239bef5d272ae586ca8acea4a661b5f2941f5a0d380e518b23", size = 228396, upload-time = "2026-10-22T16:05:35.951Z" }, + { url = "https://files.pythonhosted.org/packages/52/55/d6751572d9445485c4c5a51ec6ae11caa5c11b7ef2448cdccc884f7f54d9/coverage-7.17.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9c083e253e96fdeeaf48133fe836cac86ace2db215d235700e7f7532420a200c", size = 226486, upload-time = "2026-10-22T16:05:37.363Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a8/82d37760e537bfdb9fdee285fa1e1b45bc153293d23e2331c7befedd1990/coverage-7.17.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b88fb4d4d7608cbcebdc7ff7dbffc2115e3b81d6c33a79147cd79d54fcb080f4", size = 226861, upload-time = "2026-10-22T16:05:38.777Z" }, + { url = "https://files.pythonhosted.org/packages/24/96/70ec00a9db6d7bdd3f914eba12057360769f5cb78a98456e71fe1937deb5/coverage-7.17.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2bf3ee38a40c0570c316b9e7507c472469586906a20f289705898528c31aaea8", size = 266849, upload-time = "2026-10-22T16:05:40.285Z" }, + { url = "https://files.pythonhosted.org/packages/77/d8/d99be3bfbb208512f039c80b31e404138a86678fc929a17acd5b7135a6ae/coverage-7.17.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7c570293f001ea91598b90e90cb9b98fddee833e90bbede432cbd218331ccd8e", size = 269402, upload-time = "2026-10-22T16:05:41.585Z" }, + { url = "https://files.pythonhosted.org/packages/82/50/6cbe8b5a5060edbd21ce8109c4e900f8b7656e8b72ede6dc4d82dc0b8bfb/coverage-7.17.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:791f9ee09814dd30d9562b4f4eb037a371485ec1c830b53999d2320d20c4e764", size = 272025, upload-time = "2026-10-22T16:05:42.921Z" }, + { url = "https://files.pythonhosted.org/packages/bd/05/d3633add01e5518dfdacfdbd1be41cb5c581412dd8bccd663edf6ef7fec8/coverage-7.17.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c7c5672ecf1b497de7a0b37743bef40e3b58e3207e9fe2ed6682cb0420836a88", size = 273515, upload-time = "2026-10-22T16:05:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/57/d9/b83387780271bf9ea9f96a3aeb01c1c302c4494a927923c4fa70cd170a3f/coverage-7.17.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:337f87e62e1c5f43c8198d879f677c342d152f84d3f157add12558853cbb8eaf", size = 266725, upload-time = "2026-10-22T16:05:45.713Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/8e80b29764ae3f4d70d44952eee3f3927eb36897490fe799e400adb7f5da/coverage-7.17.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c8a58baa8eb6154c6645eadcc19b6c089deab78c24a612a83206f2a8a5992a7c", size = 269312, upload-time = "2026-10-22T16:05:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c9/e702f588fe8d83ca844f133270ad0a49bf650c8cf260551ff6f11a6b6bbf/coverage-7.17.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fb5745048ee20f796b0e3ab61831049e749acff188563e2af32f58ede957ac1b", size = 266740, upload-time = "2026-10-22T16:05:48.343Z" }, + { url = "https://files.pythonhosted.org/packages/8a/11/ee13ad14b714d75ef07c490b6e43969574a8b8b8c6d67bc741fcbacbf9c3/coverage-7.17.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:7b5abcae74750302ac4bc77351eaee87c450e4abd3b6225eacde3858e260f8b2", size = 271036, upload-time = "2026-10-22T16:05:49.727Z" }, + { url = "https://files.pythonhosted.org/packages/e2/bc/854e8912c11c54aaf416e74602000589c6efbbe1f251dc90202fdabddcb6/coverage-7.17.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4e153f0bd6509dbf837fd585f489b3328bfd98f2a718a0b15ae63032148b285c", size = 266158, upload-time = "2026-10-22T16:05:51.141Z" }, + { url = "https://files.pythonhosted.org/packages/a8/dd/96432830ddf63bc42654de54eee77a0cd6949cc65769bda06f353323f23a/coverage-7.17.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e42a835271922fe7bf98c7a05c78d4032f4e52bf5b07fafa1eadfdfc2f0ad22f", size = 268088, upload-time = "2026-10-22T16:05:52.92Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ee/230c6ec065a6629d41cc89323ceb54d37e2650c104f70fc108f4ee7c5051/coverage-7.17.0-cp314-cp314t-win32.whl", hash = "sha256:5900055351ec89fdffb34b27898815106529ded29a9496464c1ebd6313fd40fa", size = 228229, upload-time = "2026-10-22T16:05:54.255Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1d/23eb6aadcb3c00764ff2961c0ff55f5dfa2543856d932b949bbc04fa68e9/coverage-7.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5ee9c8bd13a59e301dc2aefe7aa8384633406808de051b1982775667094eb673", size = 229154, upload-time = "2026-10-22T16:05:55.682Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f1/7df117e8e016b7a3ee459e833b3456f3610505afefd071f8727702d98439/coverage-7.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a9cb9a0fb58e6fff5442969af36094ecd6976f4907f179e33b27e15355d8f74", size = 228726, upload-time = "2026-10-22T16:05:57.179Z" }, + { url = "https://files.pythonhosted.org/packages/c9/17/48032919343afa2906112d458aceda2043afdb24a2ee8eb4c60904d8ddf1/coverage-7.17.0-py3-none-any.whl", hash = "sha256:bac487dce0d2aff28ddf1a533a916f771416eee0e493960f611e49037b878e9d", size = 217383, upload-time = "2026-10-22T16:06:48.471Z" }, +] + +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "idna" +version = "3.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/e1/2069291243c926a2ff1cd706c7f3eeb9b62144bf60f77c9fb9ff2fb26bd3/iniconfig-2.3.1.tar.gz", hash = "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", size = 21209, upload-time = "2026-10-06T22:48:38.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/43/4ca9e49d27a1fcf6bece6f6aec0ea46bb9112489b93d4b688fb415457bdb/iniconfig-2.3.1-py3-none-any.whl", hash = "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7", size = 7552, upload-time = "2026-10-06T22:48:36.959Z" }, +] + +[[package]] +name = "librt" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/f5/9dc696772d241814bacac7880bac32f2930b5a6ebc1f85317b83161a011c/librt-0.16.0.tar.gz", hash = "sha256:ac38d6d8d66bf3d744148dbbc0b8e193e195a51e364ed55e224631f5721891fc", size = 219839, upload-time = "2026-09-29T00:55:32.891Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/93/9e0cf7da129a93c3dc7f45bc3cd4a660f2aaa995aa8a6f95c2583ef41239/librt-0.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:931a0bb0fcac88f263e269e46eb30ba8e21402cd3c62ca40cb97034c0693fab1", size = 149844, upload-time = "2026-09-29T00:53:31.391Z" }, + { url = "https://files.pythonhosted.org/packages/8f/26/8a90d2a8f2b2e471bb486b7aec117b8ae622715ed6c39853aec48ea20073/librt-0.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1bc17e54e5305f8d40b7ca203671ff5a9e59c1d0f8ea0f625dcca53a3984de11", size = 154111, upload-time = "2026-09-29T00:53:32.718Z" }, + { url = "https://files.pythonhosted.org/packages/35/ce/67abb46258da4d3e42ff5b141db6f38c59357bef84c7979f183b22f924f1/librt-0.16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:877698bf6bca5721d8be345f2fe09778e40ecadea8b58c73075f2b1a53666bf2", size = 494234, upload-time = "2026-09-29T00:53:34.466Z" }, + { url = "https://files.pythonhosted.org/packages/12/f9/ea7162414a16f8f1bbd3b493ad6d22b926c5471916e078350bdbca8c4e5f/librt-0.16.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5981c011b306781ce561e18e14230a14524a3d8109b97553666c942c18f31a96", size = 491173, upload-time = "2026-09-29T00:53:36.124Z" }, + { url = "https://files.pythonhosted.org/packages/b1/09/9b3e869060dd33f9989b80ba4fea306f6db8cecefcdfe6d7346ef0603f65/librt-0.16.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:afced3dfc17cd805ecf7a3d77996a71cf5f2c75aa66eb0c21a9930f4fc992f86", size = 505547, upload-time = "2026-09-29T00:53:37.686Z" }, + { url = "https://files.pythonhosted.org/packages/50/07/79007d2165f649ea93e08c0962d1d62c70af9cde77965255095bf9d96f9a/librt-0.16.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca8052401c55d7511dda6760719fda7618067e83535d7d0010096d216c34b667", size = 523057, upload-time = "2026-09-29T00:53:39.301Z" }, + { url = "https://files.pythonhosted.org/packages/61/0c/8fbaff66d0ba376d8864653f5acce1569bae27e89648671f26f7eca67ab8/librt-0.16.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1e511762a074005bb0aa569166779834e75e438370226930d0ce1866d4b6a33b", size = 515158, upload-time = "2026-09-29T00:53:41.012Z" }, + { url = "https://files.pythonhosted.org/packages/df/2e/23ff0dece76f07a4124413a57682efa0bbeb5765ac0122bc0955513f82ca/librt-0.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1e8591bd8a5a628cd7f07954c6a1592359a878bf032957a8e9057a41d644311", size = 534183, upload-time = "2026-09-29T00:53:42.451Z" }, + { url = "https://files.pythonhosted.org/packages/26/c4/e11dea21d9a29486eba78887380374189d472734fa32cc50cb37ca44d3d0/librt-0.16.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4aaefb4ba6c07e1aeebb2795c8958148f1d6f9af3b555b53d23d766edb6d67a", size = 540663, upload-time = "2026-09-29T00:53:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/75/e873ae158a8b7f5359be33e7fd6c1fbe02d9a78a3e89a77de6b0e837f476/librt-0.16.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:d92db7a0f6aee44f1baee94750457e8d2d1c6ccea41842de6268d34e8dc7eddd", size = 545908, upload-time = "2026-09-29T00:53:45.812Z" }, + { url = "https://files.pythonhosted.org/packages/ba/36/8939d3f6a93e11bd9592e6fe28d2b44f1c2dc4bed6e22e72359a91e18ffb/librt-0.16.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:378dfaffb38e59c24a87cde5713cd865d51ff7383fa12947f3907f306ea1ca55", size = 523302, upload-time = "2026-09-29T00:53:47.596Z" }, + { url = "https://files.pythonhosted.org/packages/71/14/35309f44a077f0f42ade0e2e7cd88c0cea760c661af205c01ea90d3c0e1f/librt-0.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3e0c39bdc85370422e8b637be76eb1fd07d30967551b03e62267dd156f553152", size = 565911, upload-time = "2026-09-29T00:53:49.278Z" }, + { url = "https://files.pythonhosted.org/packages/78/0c/df6255b94967f3159ebc46f08d8e783c12da6ee269ddb74b2efed63c640c/librt-0.16.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:1b384b90ab79a7bc30b566895809a636e0666f21f3cf12b54823d025b7e83839", size = 79119, upload-time = "2026-09-29T00:53:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/6b/44/d30d5a5461378c9d33f36736c6791c3b4c4ba4b1ffe0da7350aedcb2c9a0/librt-0.16.0-cp314-cp314-win32.whl", hash = "sha256:52327da75a94012e7f932f913d20d3876bed3c102be00e6c3e8600ff7bdd58a7", size = 100140, upload-time = "2026-09-29T00:53:52.205Z" }, + { url = "https://files.pythonhosted.org/packages/c2/98/769712f356a1e897df3581bb0c3100375d054a000de26099360ea65b5111/librt-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:3f0b8114c44b2ac06ff5dacd08e07e8e807ff4f46083f2a1602685122559be41", size = 120464, upload-time = "2026-09-29T00:53:53.495Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d3/ae2abccc8bdc8b063c1613a77686e17b74e9d3d60cfe6f12c63fa2821b9f/librt-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:8caf96a4ef8fb27d0ac0d1ad8337d26a240acd4a02fe4345d0a8f264753e8f99", size = 110889, upload-time = "2026-09-29T00:53:54.817Z" }, + { url = "https://files.pythonhosted.org/packages/e4/26/0737d4be058dd6376eade7dd8b380d4b869b6394cee929393a5a431c45bb/librt-0.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:953107e2f68d0f3512c48f898b0dbf0ce5cc52bba0f318d847c985dc555ee4cc", size = 159838, upload-time = "2026-09-29T00:53:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/01/96/9bc96531d7c620e9949af904470f02e3fa8f35129ab8e8f281c51eaa3788/librt-0.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad37d5b9abd49c9a655dcda7ea52a8a752884062ef1ee71ae17c2f2a0f81fe6a", size = 162018, upload-time = "2026-09-29T00:53:57.532Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fa/b0289dcb186eb3f97221ba00da5f4bd3ba7fa5e99752d48aa8d336615334/librt-0.16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c4aa329c17bd1aaea4f6e89335d8ccd494b3a5830b6654462273e50e11023f0", size = 703983, upload-time = "2026-09-29T00:53:59.024Z" }, + { url = "https://files.pythonhosted.org/packages/d9/ab/05ebbbde7530fc5eeb58fbd1581522a9f64f132fa703c4c595eed6e14760/librt-0.16.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0ead24d2562a49473dddd9efef8581f020007eb0054389c3ee3ffad38b1ca4c9", size = 683847, upload-time = "2026-09-29T00:54:00.671Z" }, + { url = "https://files.pythonhosted.org/packages/a3/75/f52aeecd4dbadbddf80725ba7de126d8bd5d0eb66247eae17a81ed90dd4d/librt-0.16.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02118f56a9c36ddd07dfd9b919d9ecc117ba20a90987d56aa4c429fa34509188", size = 698045, upload-time = "2026-09-29T00:54:02.247Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/fa277a835cd6eb42380591ceb85f48c5b4d2b2d7e2cb9869e1e17d24d237/librt-0.16.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e29522c62e28595ff7e324c6834ade51127707f0e255b18d1c1cf03d39c1048", size = 724640, upload-time = "2026-09-29T00:54:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/25/e4/2cf64354f3fde8ebd591b3b48f96510bee24ac5f7d1e567adbbe210abdbd/librt-0.16.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ff4b2367926b69c6215635902cccb04048e73094e9862900d27cb2c6bbff143", size = 731748, upload-time = "2026-09-29T00:54:05.741Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c9/c180af3e94e01aa529fa93d7733fec2abc47f222345400cf21d5481d5f8a/librt-0.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c6f1b27bf1632a7e016af9f145f82be95e1edd7721a646505c21059257cb5a04", size = 754960, upload-time = "2026-09-29T00:54:07.38Z" }, + { url = "https://files.pythonhosted.org/packages/95/d6/01073aa78c58f356b10d9c57b3fe9abb143df338142bcd316df417b89db0/librt-0.16.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5696d7f52e7b37217cb3a8f92c744fe835942602fdd4c1a8bc4741d3bfdce15e", size = 747000, upload-time = "2026-09-29T00:54:09.06Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d8/1de3783908658d697a8cfc00582f61299ffba7796d7260c112e4700b1109/librt-0.16.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6072e92dd876ff6ceeb6cf371e35e51f479349837391341f479b08df4564242b", size = 749569, upload-time = "2026-09-29T00:54:10.702Z" }, + { url = "https://files.pythonhosted.org/packages/b7/32/e817f66c96d6caa8bb8435ff93c4c220624d59efc506be59ab98fcd01d0c/librt-0.16.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:39ca4f2f2fe05de8e63493da592d84311adabe5bef52b193851981da9816b302", size = 729483, upload-time = "2026-09-29T00:54:12.25Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c9/23992ccd2b9d22798fdd0f61353183a47414e651da782ad83eb680f50833/librt-0.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9807485a908f00355820f18e91e045ffdcdc5adb68aaec40a1e2b88c5f7bba1", size = 776584, upload-time = "2026-09-29T00:54:13.837Z" }, + { url = "https://files.pythonhosted.org/packages/67/3b/e8af957f08e6e2e8e566b099e43d2748418aa565df6ee1d9d3331209fdfe/librt-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:94be5cb7bca4df6201f4183e9e4fa2086c655283d20b38cd84500a69057575a7", size = 104402, upload-time = "2026-09-29T00:54:15.568Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1c/946e6443d7cd32347a086043395e421e52fd603c9163d2ec970ceab8eed6/librt-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d46ca272b251d033dd4527b0dec5f261a28a52bd5fa0f99c117b0a1f8588cc2d", size = 126227, upload-time = "2026-09-29T00:54:17.165Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a3/bc4f9959d3c62bcbf9e5fd3470a8bbd8bf33224ed4c25d7fba173201b8ac/librt-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b9d6d4b14e92d876f8026b54c20c445f36425214c1081dc76f74e40db386b82b", size = 116493, upload-time = "2026-09-29T00:54:18.567Z" }, +] + +[[package]] +name = "metaclass-registry" +version = "0.4.1" +source = { git = "https://github.com/OpenHCSDev/metaclass-registry?branch=main#fb7c871a78c3087aa65c3a7199a1068cff858f33" } + +[[package]] +name = "multidict" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/79/84ddb5ba16c4eb2c69c71db76ae3c579fe546e511f7170c7e27eedbab7c1/multidict-7.1.0.tar.gz", hash = "sha256:61a4e5d81b8d4e4ad61964b230129e7a2b914793d96289029078fc9009f074ec", size = 362341, upload-time = "2026-10-09T20:31:38.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/a7/c9f5c08348a6f903143b631546e22becf1b704a1304b17f7e1b9850308d2/multidict-7.1.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:128ea4142f81a79d430f3d0eb55206093e5eda03a12abbc7b03c34748ff6116b", size = 100435, upload-time = "2026-10-09T20:28:26.111Z" }, + { url = "https://files.pythonhosted.org/packages/82/60/92fe617008c74cc019483b1c59202c48738c6f637ad5b12b36f01e21db43/multidict-7.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:439a19f7fbbff232ce96682c57e27030b8ac3a4b8121484c94f04bf99d08bfff", size = 95267, upload-time = "2026-10-09T20:28:27.856Z" }, + { url = "https://files.pythonhosted.org/packages/17/78/82182f311d673f17de15bc7f7465c7ea5cbd2987ca3a6bf6546fa721e9d8/multidict-7.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8090c35199d6b7bc6426bb8bdaf341e64f295cc2624a1fda7860c0837f1acc03", size = 97233, upload-time = "2026-10-09T20:28:29.817Z" }, + { url = "https://files.pythonhosted.org/packages/63/25/af4e482d053fd73b4b1d60de3ff5578c6cb1d319d39d3f2a454d65409835/multidict-7.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b7cc5333fcbfb27327d12612ed72322f221b61c2b69deb1155078c964f86e1a1", size = 163070, upload-time = "2026-10-09T20:28:31.495Z" }, + { url = "https://files.pythonhosted.org/packages/df/d5/eaed52e199451dac445305fb2631d3f4e7ac9536aeef01f23622a1d93f90/multidict-7.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b0e0040b0d8dd89bd0af9ab18901981e344ffba68bb30b8eabb4eab6c303279b", size = 94582, upload-time = "2026-10-09T20:28:33.263Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e3/ff58ecad5161baa98dec05716e2745449446f2424a9c727464bf452f7fd1/multidict-7.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:33376418ab2846b931a72b36cfa16810befc4f49485d0b3f4dc054a4d6d00038", size = 97406, upload-time = "2026-10-09T20:28:34.981Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a2/ae4eadf02d4bc035baa7cadf5548ca4fa441517193f76ce1aeb5ed276ae3/multidict-7.1.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1101aea5c3eb1d26e090b931c693488af0db9f3d52e68be8d4cdd807dad9841d", size = 449908, upload-time = "2026-10-09T20:28:36.659Z" }, + { url = "https://files.pythonhosted.org/packages/91/55/bd5101ef760d1af4c3f246330b29b48ee22bfdd5a83150b713dd93b431e2/multidict-7.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f996b19ac89e0dae65821ce65f788619e4286f78c62d005ecd3b75b5d9c0892b", size = 471605, upload-time = "2026-10-09T20:28:38.561Z" }, + { url = "https://files.pythonhosted.org/packages/4d/8f/3dea8a28b0416ab71d47c8ce274bb3cacedde2d9838647ac67cdaa3d48e6/multidict-7.1.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e0d91a4bcb59ac0d7af0d8e0da737332e1b7fe6831e53e47819b1b5349d431b2", size = 433523, upload-time = "2026-10-09T20:28:40.913Z" }, + { url = "https://files.pythonhosted.org/packages/24/97/805d2aa4f746cc43cf4b206f1b1bfe2566add95bbb145abd3d9cf61858bf/multidict-7.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e96d67914ddbf5466e4476a1cd7ff30a332cbab85ed895207acc3e58c979b6a7", size = 488277, upload-time = "2026-10-09T20:28:42.851Z" }, + { url = "https://files.pythonhosted.org/packages/dd/7b/eac247b7e76f6010071c7c2401da5255eeffd1b2ac981925e533413d21ec/multidict-7.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:170ba61761f59ab92afcc86ce5534a3f3d0b07c38b339b950a83213f22dd86ec", size = 501888, upload-time = "2026-10-09T20:28:44.75Z" }, + { url = "https://files.pythonhosted.org/packages/87/58/de62e27b09dd15756265765945f68f1c0d1e23eaba09e2e109fbb4112425/multidict-7.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33389fe084e5426d9fd85d7d9ca91a29cd0d88a83c7c96e411aca49a3f9967bc", size = 474883, upload-time = "2026-10-09T20:28:46.704Z" }, + { url = "https://files.pythonhosted.org/packages/21/fd/afb4e50ce3b44707b5ee39c6b5fa1573bdb50c4be660bb35b040362b5170/multidict-7.1.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77024596b9046572c4e90b34c1ff212346756dc48933f90c53cf6e233660788d", size = 424327, upload-time = "2026-10-09T20:28:48.681Z" }, + { url = "https://files.pythonhosted.org/packages/23/70/96c9abf933c4edae53b8a1c8d3f038120a3a60d363f16c6d94eee9b04851/multidict-7.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ff15531a376dc6f35984443fd1429e4b150c36ce27633e7cc52a9e5318546e20", size = 459255, upload-time = "2026-10-09T20:28:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/24/37/dbc1dba26dc1c9e42aa07ee01908131a67fee0e79ebe5c1d0e7fc00aad55/multidict-7.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:544f2642a456fa264614e975d921540ee8c3b368b04d5aa1ddbec33241b13e08", size = 432476, upload-time = "2026-10-09T20:28:52.361Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0a/eac70cc1461668bad8253a2da727670a56293eaba112aeb4079af8cd37d9/multidict-7.1.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08834fb8b20e1a985c70e8380a10940234b4162de62694458727330376e58b33", size = 450030, upload-time = "2026-10-09T20:28:54.252Z" }, + { url = "https://files.pythonhosted.org/packages/23/2d/eb40652ea74f96df859c7c5d38f9997f420b9dc404300dda0c5745327a6f/multidict-7.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b4908e17867930b7ac77f89a18dc67308c67c511f037d8580489be86fb585912", size = 476169, upload-time = "2026-10-09T20:28:56.819Z" }, + { url = "https://files.pythonhosted.org/packages/44/d0/7454ab8335bc4ec9f8abcc4c83a314bab060f22db6da8436b396e07ed21a/multidict-7.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:9b24e1f93b9b586ec03bc7bea1bf021ec90bf2528c729195028a3ca1c266b3f9", size = 420459, upload-time = "2026-10-09T20:28:58.744Z" }, + { url = "https://files.pythonhosted.org/packages/60/7a/cedb46b287b720a2c4eda2448faf162612d0b718beb32581e35ccd9219b3/multidict-7.1.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5c93473d0d7cd9bbb370973a9679a62f381c7050d7dff4ad6aaa92e8650f5a79", size = 483071, upload-time = "2026-10-09T20:29:01.114Z" }, + { url = "https://files.pythonhosted.org/packages/9a/11/e7ded22b008546dd30ae8c979aa5ac3282cad98876f7d5bbe93c1f2409a0/multidict-7.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bf14cfcc30b097583d698a6e2b8b68c9bcffab277c485d481881958360c2938d", size = 467470, upload-time = "2026-10-09T20:29:03.388Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f3/0136144cb0b1731fd93475cea75d974a55009358d3bf61dd55e2709a3dca/multidict-7.1.0-cp314-cp314-win32.whl", hash = "sha256:86bc779a0896e59e4be30a5be5cd6eeffd0b40b6f0e75e730218736b7bfc6f5c", size = 80205, upload-time = "2026-10-09T20:29:05.736Z" }, + { url = "https://files.pythonhosted.org/packages/74/d9/a62a690d780febc171026d3e3c5700fc8387aa3e5f77cd04ada33d23d3f4/multidict-7.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:6c9fd50f636a8fa9cb6324cd3eac962fec2bc5bb432452a3b583583a1059acfc", size = 92923, upload-time = "2026-10-09T20:29:07.337Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/eb7a1c34dc37c5f2c6aef52e6861e6f1cdfc6c685dcb72581eb218c00953/multidict-7.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:e6906aa4bc62cde2c8aeb8a99a7b4401b241e274ae7b11df67d863d61ab3d5de", size = 92851, upload-time = "2026-10-09T20:29:09.15Z" }, + { url = "https://files.pythonhosted.org/packages/ff/3a/019abd746e8fbed9edd74b81259f20b278c011cef918868506d9b5bd6566/multidict-7.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fabfdd4cf97db033196b51af46b8a681d4785c2a66347f2a5af1b4bbb1182629", size = 177429, upload-time = "2026-10-09T20:29:10.989Z" }, + { url = "https://files.pythonhosted.org/packages/f7/41/c8dda935231305d9b83c4a4cc5b5b9e323615328d4704f00ba2cf6d89916/multidict-7.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a177a0ee5cf19931dcaeb3f662bc562754cfa4f4ace2351d9da24a954ef7db94", size = 102003, upload-time = "2026-10-09T20:29:12.78Z" }, + { url = "https://files.pythonhosted.org/packages/af/23/3c3e79a222eaaa59a32648cec34709d848657be5339f96876075731e7f8e/multidict-7.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0ae91de396d5c4ac97cb24dbada3d5c91a51454781e0a70476b008f4e879e4f0", size = 104861, upload-time = "2026-10-09T20:29:14.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5b/04637e7cea5729466fb89048520fde167c5404df1beedcc86f18aeabed7c/multidict-7.1.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:160bdb3520fdadcaa21e1b98aab2e011265070814ecab3804eb61674becbd400", size = 463221, upload-time = "2026-10-09T20:29:16.697Z" }, + { url = "https://files.pythonhosted.org/packages/2d/42/b121767de213a9774399c200cb877b19c2e1e5a0b0a6bc8407c7325fa37e/multidict-7.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c2144785e42527404bbd5cfd11981fee4abe59a22aded0e498eb711a831d3f3", size = 502241, upload-time = "2026-10-09T20:29:18.657Z" }, + { url = "https://files.pythonhosted.org/packages/47/0a/1cccd17ebf39776df7d8a6c99d066fc3ac1823d044cfe86a22ebbf42b841/multidict-7.1.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7d0b4fec6a8d02d7e95de5cfa913261820f1ce04bd4c0381924de0da523179b8", size = 457128, upload-time = "2026-10-09T20:29:20.589Z" }, + { url = "https://files.pythonhosted.org/packages/19/a9/0616d20dee16255f8737d9017288d9304dabf3695ac0071baca25d00c713/multidict-7.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:943a9bce22180ad0f4d32d1b402a0949a4ecfe5a1257b47f54a1b51981d81b86", size = 511842, upload-time = "2026-10-09T20:29:22.569Z" }, + { url = "https://files.pythonhosted.org/packages/91/68/5a406b82ecfeee50c097c05fd519c836a61815d81c4a9a8c523d90fb4b35/multidict-7.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f76ceb623f7ff50df46ac57e1587c479d87a5766319c4f43d0c0a5158896afab", size = 519852, upload-time = "2026-10-09T20:29:24.527Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/d42234f9d4f0e33885c70149112bf62aa4436045090c15cec0810ada0b2f/multidict-7.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98beff85392ce435b28a0971ec21cade61ce8be8b632c9d855475a28ef92d31a", size = 500322, upload-time = "2026-10-09T20:29:26.569Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c6/323e921a9812a6ea82978d4e1d9713e0b709023be037e6e4c14efe5472eb/multidict-7.1.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:71196ebb8d523148e5975396a444de02367f204b53b14e26794c96b2be0ed742", size = 444070, upload-time = "2026-10-09T20:29:28.52Z" }, + { url = "https://files.pythonhosted.org/packages/93/87/109b7170de2168294f3e562f9d10caaaf946628d614b5a57246279acc1a6/multidict-7.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:35534b366410a36bb3d6f788691e37a76e4d1da48326b0ada3e5032580dd76af", size = 484862, upload-time = "2026-10-09T20:29:30.504Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/7f1f63c9cf13f47036c0c64c607e6eee87c94076e3c6876abb6990ee62ad/multidict-7.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b4674b12701c3fcbdf7f88b9e4479701c93bec5da9eb576140d5fcc0092990af", size = 457314, upload-time = "2026-10-09T20:29:32.6Z" }, + { url = "https://files.pythonhosted.org/packages/93/ce/7de8b4ee6a847cc951915c279fc388127ec32a7c14aa7a01c4ad692be575/multidict-7.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ead852a5e906a43fcb6784eeac480f6a67919a51d480c1f80d32ddf9d615475", size = 462672, upload-time = "2026-10-09T20:29:34.776Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/02dd089475983b1f5b8729319caa0b02fa3a60b527f27c86b2d53c24ca11/multidict-7.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:afe36ca503c2ffe30fb6df82b20389fa3c4035b5d65888a61310921cf3ae91c5", size = 498109, upload-time = "2026-10-09T20:29:36.763Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/b9bdc59aa8e3eaf1f9e5d2460bbe0c428a01250d44417f8ccf965a8f6fa6/multidict-7.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a5f0bebb10aae010d3c9ee3abaf83ab2069c718457aea09c15532355dd7e061f", size = 442260, upload-time = "2026-10-09T20:29:38.872Z" }, + { url = "https://files.pythonhosted.org/packages/fa/83/21b044885ab81bf5c03ec5c4c16aa449977e428c5c122e38969481cec1df/multidict-7.1.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:b9d9b7d72975521434368fe8aed3f6b522060bf271adabaa5ca6c87c0c08e168", size = 498916, upload-time = "2026-10-09T20:29:41.091Z" }, + { url = "https://files.pythonhosted.org/packages/95/8b/31686730092e349ee2255fa630945ca344fc62e76f0b18d65b0f3dbb0ecf/multidict-7.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:274023bf952f849e0d05eba28a4c1f65f9796430d2b09ec16539386c0f76554c", size = 493525, upload-time = "2026-10-09T20:29:43.192Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d0/c9fddcd7ac42b70a46ac9cf15e21eb527874164e14d418fcb50ce7190a4b/multidict-7.1.0-cp314-cp314t-win32.whl", hash = "sha256:7e0bfa161df365ba3c88899ee3b7c94755200967284bdedef8c1b8b43e2c0f2b", size = 86481, upload-time = "2026-10-09T20:29:45.188Z" }, + { url = "https://files.pythonhosted.org/packages/24/ab/ce727c06680e72b5d381caa8587f561cb6fea1bfab6ba20c877019768a6f/multidict-7.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:34a35be8fb82d37087e8176aba907b9459f03d0e293c80f574c6337a436f4eaa", size = 99676, upload-time = "2026-10-09T20:29:47.441Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e7/d116d7ce514d04d9bf63774ed55e8033e2ce15ab5655a597bb947c53dfb8/multidict-7.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:d7dd46a8fcd7653c09ebe67eae9d4cb6636c7a905d9cbaf587dabcbd4eca6013", size = 98726, upload-time = "2026-10-09T20:29:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/86/a3de309c5e28ee85b314d0e3ba0e0dea6fd361c313322a05e67be4656e1e/multidict-7.1.0-py3-none-any.whl", hash = "sha256:d9ef29cfd98e17085b4f91bba8fa1570bec6787d5c52ce653ed33a58785585d0", size = 28087, upload-time = "2026-10-09T20:31:35.945Z" }, +] + +[[package]] +name = "mypy" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/af/4e516a05d3ca2eb9283e9ec45b2c02225c1514dd6da49fd3c9eaa6639370/mypy-2.3.0.tar.gz", hash = "sha256:465965d41cd9a2726694e983e8ce7113259327bec798115d1e1dfa2a52fb666e", size = 3988104, upload-time = "2026-07-13T11:34:53.387Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/58/fa0ae047da911f540284009b4f44b96fe09d83c076d7c103e9d645f46303/mypy-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea317b060ce83e26050f8f9e4d7d6bf44ed7597c8ff9990bccffbb9d1d8522db", size = 14941909, upload-time = "2026-07-13T11:32:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/15/14/2ba1d61452d7c2a7fe12741e8d374e52b183476b07aa7f9e2a0d02b0720a/mypy-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:094af99f92638aa92852326188b85a89e50f4a472f44827c03362228482f0762", size = 13967581, upload-time = "2026-07-13T11:30:00.587Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5a/483fb9e5ffbbb1a28dccc7b0a13d141b17ac769b6c9f488c0a0c63698962/mypy-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de121747278144fc9ae7caa2e978cf5df12aebc82933182f5b3b86081a30baef", size = 14168807, upload-time = "2026-07-13T11:28:48.6Z" }, + { url = "https://files.pythonhosted.org/packages/ae/77/70d7a10732063beb74ad713682cf871e88f5c5fa39bfc8beff8a524bf9cb/mypy-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37fa4de896a84e2dc9200d91e614c22563b43d1a266789d4bbac7b22ebe6192b", size = 15200144, upload-time = "2026-07-13T11:31:25.283Z" }, + { url = "https://files.pythonhosted.org/packages/56/72/766218ac783be4fdfcd699b90037b63017348a3e86fb2c1fbfb18302637d/mypy-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f1b3a98dfd21058bc759bb3337d5d1f61d0fdf9f3cf9c00f4291790fb5427bff", size = 15460389, upload-time = "2026-07-13T11:29:29.077Z" }, + { url = "https://files.pythonhosted.org/packages/38/4e/8a9db7411ecb8ec0cb1fd05dba432f28bafffcd38b4e887714a4a0506689/mypy-2.3.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:944c665d984157cb96a679dfb7a4a81dd1d36b24b9c284b699514e6e626b82d4", size = 7753664, upload-time = "2026-07-13T11:29:08.147Z" }, + { url = "https://files.pythonhosted.org/packages/65/4c/c3f8bfd6ed0e5e38b5a244403b27f821d433443df5a15a278417c10a3a3c/mypy-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:4359424140d985192c778c1ce2c114a10c1ca58a381ed79cfa70d37df94b299f", size = 11417237, upload-time = "2026-07-13T11:33:47.467Z" }, + { url = "https://files.pythonhosted.org/packages/3c/00/89a32eaf5ccf174bc4f90db0eaea5d70636c01b8d49f384bdab2e8834390/mypy-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:3dd0bed92c4bdec57c42505b96416fb9e6a5aa7be84d2809bcd5f2ecec2860d7", size = 10389252, upload-time = "2026-07-13T11:31:43.81Z" }, + { url = "https://files.pythonhosted.org/packages/31/56/104f93d69aa9f339b6b9d3b0a7faa699b8b466c942cf3ae86cc2a2ec0915/mypy-2.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:691fdc37132b1ae628d834f672e74de83462d9fb4aff621835767fb43a8dd373", size = 16385495, upload-time = "2026-07-13T11:29:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/d2/03/f1d2123313f55efafdd27706960f43a771c62f1b68426c76043f3ab9ebf3/mypy-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:aec15d465d477558fd842757b487849007311cf3897849cdda0e3162ac0ac556", size = 15098155, upload-time = "2026-07-13T11:30:40.301Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5d/d5f9200399b445e81726c4f23becee33f233aee81c72680b1ef3a258b641/mypy-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b352b7e49f5e6576009e8df730e1ff4f915cb565b851b396d2ffe2f5a6f5da88", size = 15514155, upload-time = "2026-07-13T11:34:38.569Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ce/69977c555f08faa3190cfde44189b89dbd56861b1ab97aa18fc5f3a2e4a3/mypy-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c6c6bf687b17f90dbfcad95b960d32eaa0154c00da45f03ab50bf8952e047fe", size = 16766351, upload-time = "2026-07-13T11:33:29.195Z" }, + { url = "https://files.pythonhosted.org/packages/bc/92/6648b6caa3ab9e00f9ac0c2a78307805f873dd48139b24a6f6f7c3667bbf/mypy-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f4ed18f111bfe2d599bca7468e7f9251042c1c2118f762c8de2766a56d773c60", size = 17043490, upload-time = "2026-07-13T11:30:53.927Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ab/0dc91d80f3f016634c68d451f294a97320fe903a9b6f90b9e57b3f7f1717/mypy-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0b025a93cffb9781d231f232be07a17912f35f10a313c24f301c81e842870654", size = 12146869, upload-time = "2026-07-13T11:29:38.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/b5/4c964d02634ba81f4d1c84838e5c5b18ab06d13ed568960f5d6318495ccc/mypy-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:adebc76aab4f3495a88b41d48aa4aff0c03f2822501da76625afcca5975f19e5", size = 10965113, upload-time = "2026-07-13T11:28:07.056Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fa/fdc54fe583ba3cafbcedfb70eeeaf03849f75b1827a07096c7bd996f582d/mypy-2.3.0-py3-none-any.whl", hash = "sha256:6b1cdb579446b60432432b2b2403a6201b4b475a004d7f488511c9ba177c9e88", size = 2753292, upload-time = "2026-07-13T11:33:18.48Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/e6/d0abbb256b44e6a05748c6942511ff67db8dbb068de0c35b24bf1b395b09/platformdirs-4.13.5.tar.gz", hash = "sha256:05831adf87d392760f4b5c3a0a95579ab5a10b18b9b9bd43680dcb47544788a0", size = 62362, upload-time = "2026-10-19T01:02:11.778Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/8a/1d8cb3842909dc193a73835adedf412e32e18f8ffd59f43983949622fe6f/platformdirs-4.13.5-py3-none-any.whl", hash = "sha256:53d3ec4280b815c1e0b02edad7f9ee8fda1ba9c0865dd9c2e6e63849ce82fc1e", size = 32779, upload-time = "2026-10-19T01:02:10.17Z" }, +] + +[[package]] +name = "pluggy" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/db/7fc19e6f2dc92a966727031389fc2e08b558f0f25eb7403c1119ad4713cd/pluggy-1.7.0.tar.gz", hash = "sha256:d1eaa46ebb595891b860ab086b4d09c8588af65ebd4361b8e8f4bb8920b90ba8", size = 123304, upload-time = "2026-10-15T09:50:58.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/9e/2b38731e0fc536806f16490e1a12d7f0dc2a1235aa8cc07bcc75416a7daa/pluggy-1.7.0-py3-none-any.whl", hash = "sha256:7dd7b0d8832ba3cb632c306926ded123429211b83641b35dc5c41ad2d34f9bec", size = 27082, upload-time = "2026-10-15T09:50:56.808Z" }, +] + +[[package]] +name = "propcache" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/9a/9fbf4e4ec0c2d7f1c32519fff782ef467859b8faa9fbc5331a96f6395d43/propcache-0.5.4.tar.gz", hash = "sha256:ff6b113f50bc066a698db5d944d2c6dc7507168dd3341e255a8892fd0715a558", size = 61545, upload-time = "2026-09-16T00:17:14.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/c9/07e227b930c8ae513b8ef1aae3793499be097bffcdf7aee4fb8b33db4cd1/propcache-0.5.4-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e6720ba44ad7e72174314d0e1fb0172494cff5c73a3a8a2159c3d2402ff15565", size = 85933, upload-time = "2026-09-16T00:15:16.073Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e1/6710bb44510c4e4a8e0f004bbaf3cecfd048141309c77bae56d4e5a6ebc1/propcache-0.5.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4cfe0a92ae30151869e67a4b5f5e105e4e03ad30b3f38e5211b5bf77d0881993", size = 50179, upload-time = "2026-09-16T00:15:17.377Z" }, + { url = "https://files.pythonhosted.org/packages/e2/22/b533b493d7025456f44518b33e53e000021a20fe7c27b88cf3d341df7186/propcache-0.5.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d759d05634f1b038fb625a66662a8c85e5a8fec912da381b5149ddac107482b", size = 51942, upload-time = "2026-09-16T00:15:18.589Z" }, + { url = "https://files.pythonhosted.org/packages/f1/74/70ac8430e28f21e442c7bcb964eb46c4363f6881ade4aa0e978bfd8d503a/propcache-0.5.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:251c63dd46a0659bb875cb254dc4c1e79ee91a847c737cd62373295afc2235dc", size = 232647, upload-time = "2026-09-16T00:15:19.905Z" }, + { url = "https://files.pythonhosted.org/packages/72/95/f222f13b6fe623310be0eb61a673bf26df439ce27e563ca8e422d0818777/propcache-0.5.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a8d5ff04eb1f85698a78d20c62a14676e7b960dcafde09a388d60ad377d355d", size = 241541, upload-time = "2026-09-16T00:15:21.3Z" }, + { url = "https://files.pythonhosted.org/packages/a2/3e/763e370340db16115c5e63ad46e21ef0770a7f06928b3d3b62d8f8edfca4/propcache-0.5.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b9100a93b372418d8688f3f2a3e5b45c64d70ca4d6176e121aca1e3bfc1e32f", size = 245332, upload-time = "2026-09-16T00:15:22.802Z" }, + { url = "https://files.pythonhosted.org/packages/96/d3/e97cd6f5de2176bd90ed4076c7a9b5e09d0f0b9687d00a576507988bb62c/propcache-0.5.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc07876cfb079b6f6f36d21ce75784ad6c2c6b563eeac0ed26c2fa2669b85df9", size = 232757, upload-time = "2026-09-16T00:15:24.374Z" }, + { url = "https://files.pythonhosted.org/packages/f9/4c/6766e5f60bcda26d244333aa71d0a702c1c9b21b251d543c7af5953d1eee/propcache-0.5.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0951315a6b3142ee2167404d707743f0157c110091342b1aa0accac5cf0e4acf", size = 204389, upload-time = "2026-09-16T00:15:25.667Z" }, + { url = "https://files.pythonhosted.org/packages/b8/5e/ec4bb09a70b26ea99d76a8292c3383b960b296de2b347ac9986678f1761c/propcache-0.5.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bee7d3aed13d56f54e681df38c3a23031bc9e3863f687d9d598825c9146acd7d", size = 228217, upload-time = "2026-09-16T00:15:27.11Z" }, + { url = "https://files.pythonhosted.org/packages/e1/7d/b53922ba7d9e5bf797324e63aa05906ec240871899f779628df068743e2d/propcache-0.5.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4e985382be6d15da8d0c2710a6fa7b9070fc9ecdeefb7f580e88373984ec8be3", size = 216947, upload-time = "2026-09-16T00:15:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/ff/39/b62eee45e5ea4de094a258cbb3b01c1e856ca51ddfd95b43135c5effd1eb/propcache-0.5.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9e9ab13760aa8b6d0881ae7cb04fd891d8d490cd2554ea8e79bb278399169bcc", size = 233457, upload-time = "2026-09-16T00:15:29.977Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a9/feec61ed296d993db9dd097e0f6723e3f576a647722367547495e4c5b05c/propcache-0.5.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1b2f3bec4261a94019575481c726c29850f72e27907773c75b1de421e20e9f9d", size = 204131, upload-time = "2026-09-16T00:15:31.74Z" }, + { url = "https://files.pythonhosted.org/packages/92/4d/411ef380cddad28dc001f1c6d75ec72c76cd3817030f68ec1ccfba0ec6c1/propcache-0.5.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:720cf832eb2d0b0dfee129cb3335a26f6ce3cc45ee1187e8f0731758caa16792", size = 234820, upload-time = "2026-09-16T00:15:33.087Z" }, + { url = "https://files.pythonhosted.org/packages/15/37/c988229753629ef1cfd5198337a83e624780ea2b3787efe9e747c05aad2d/propcache-0.5.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fb0a5be8d9aa213150e8d8148a42aca4984b285bcad1e69587dc4298edd929b", size = 228350, upload-time = "2026-09-16T00:15:34.533Z" }, + { url = "https://files.pythonhosted.org/packages/12/49/5ef1c5cf98591da3c5b952b39e6a298084cc1ce353bc70f85e82397a5036/propcache-0.5.4-cp314-cp314-win32.whl", hash = "sha256:30cc1cebaf9aef49db06357a50398323ae04d70460c0491837d026ab7d6452ea", size = 43578, upload-time = "2026-09-16T00:15:35.957Z" }, + { url = "https://files.pythonhosted.org/packages/1e/9e/a0ac821a2229186af5e2e3c3635a78abb23cfddca57f38513ab5d70420f3/propcache-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:0a095db8e15a6020db149ecbed6461939fe74f6acaa3ae8b702a1fe8c38cd983", size = 46304, upload-time = "2026-09-16T00:15:37.655Z" }, + { url = "https://files.pythonhosted.org/packages/a1/19/c8d0d36a9d16cba5dcee67d389c9333b988c8986a653a61c00a451817a46/propcache-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:45488d1a5f9ab5bd90aaa1ca20f50fe1922b8ffad71a2009d2adf41355897aac", size = 43440, upload-time = "2026-09-16T00:15:39.091Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e9/42f1da77cacfc184e6ec929557ef653b7961bbf6f1da460b9221273948b3/propcache-0.5.4-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:53eaa697c4d0422ff4cb714d00231b43352064d97b944033b30c1d57cc506ec0", size = 90672, upload-time = "2026-09-16T00:15:40.306Z" }, + { url = "https://files.pythonhosted.org/packages/cf/2f/4b79940908c6ab8c795097c102999d7bc1f7e0b8604dfd1c232f9d99d67a/propcache-0.5.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:886b59c4d28ca97dd23b025fdfc50a0356be934efbbbca89ad26230067f86fe5", size = 52586, upload-time = "2026-09-16T00:15:41.575Z" }, + { url = "https://files.pythonhosted.org/packages/eb/07/02196ae6320c110235bb343f90dbd34be41f8b8964a3ee30db84ec12579e/propcache-0.5.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fa15757fea1dfcd5b7745cad9f4638929605531bd4018ab2adff7955f1a403d", size = 54335, upload-time = "2026-09-16T00:15:43.027Z" }, + { url = "https://files.pythonhosted.org/packages/6f/44/f48b9a131985659924df5fa5093f68fe72c7ee375329802989ba3126efc6/propcache-0.5.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f0093ac3e9daada202c2082439d414a625c57184727a46e112a3fb2a81cb788", size = 297567, upload-time = "2026-09-16T00:15:44.373Z" }, + { url = "https://files.pythonhosted.org/packages/04/a1/418d956d2735139f77fc35262179f1f52c23aa666de5a8ab3819c1ae7854/propcache-0.5.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3cd3a7edb6b95b9b33998135ebfa18d709da82290fb8f27c858970b5a12c8b56", size = 297477, upload-time = "2026-09-16T00:15:46.048Z" }, + { url = "https://files.pythonhosted.org/packages/69/fd/ff811fdb6d3d3e67fd9bbfb75881675d34a42d0ef29a45d33e3e233dde07/propcache-0.5.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c174bfd1c48a1b51a3078e95586dde718374bac79719ab3541ec9e74aec40574", size = 302669, upload-time = "2026-09-16T00:15:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/fc/57/527910c455b5ec62f6871bef45d4f79fea16cb8c966ba0d4a07f0339ddc4/propcache-0.5.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a219f0ac59817a9114dd2aa57c13180f993e819ba658c7ddab4b66ed1ee0d370", size = 287908, upload-time = "2026-09-16T00:15:48.99Z" }, + { url = "https://files.pythonhosted.org/packages/1d/86/f69ab82707534a0cb2057bdca04f9200a71214c7551800f9d34d6ac39e4f/propcache-0.5.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:17a7400cec0256f0a71ae71f9da398f9894c956ff6668a1c9d317b3367316320", size = 249804, upload-time = "2026-09-16T00:15:50.486Z" }, + { url = "https://files.pythonhosted.org/packages/27/19/60677af50d93be4256213de7cd487f056944c048b9c0b6f2e45b3a30f666/propcache-0.5.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:978f28401afbc76cdc3df9e1717b4229a06b626a1dcc75db4e1f2beb3884c3e9", size = 282344, upload-time = "2026-09-16T00:15:52.029Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f7/a0057808a91fb3b6a5f3602b528f0cdcb3d53e0ff8315d73fabdfdf8fec4/propcache-0.5.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4a1f4f5ffa55dce6307631f3cb2948e117e665966ea512e0d502b16c24f567e7", size = 270167, upload-time = "2026-09-16T00:15:53.466Z" }, + { url = "https://files.pythonhosted.org/packages/83/c8/f4a865490df0dc0c8531d4e59ac411cb6dc24bb255d2396a6f1c60a368f4/propcache-0.5.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:213bb68d9ced5cf2bf717b1071bf2b09b4b04c426256f9fe6d054c60318424c4", size = 286551, upload-time = "2026-09-16T00:15:54.995Z" }, + { url = "https://files.pythonhosted.org/packages/b0/67/b4faebde9da4e8173d0e5a30e8cd31335914af7ef350b988f27fec588cfd/propcache-0.5.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:286867fb156488c251a3721766e380ac4495e4fd6b51aaa1403d89ce7f4359d9", size = 249595, upload-time = "2026-09-16T00:15:56.505Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/52e1dd5636e9f5a27f6b5a4b4e2f33c322fd72afe956c397d82523ec4a80/propcache-0.5.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:445ee3bfb46e85838387fb3c536a73cc0b994dc192b004e40e170adc54aa2a7e", size = 286700, upload-time = "2026-09-16T00:15:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0e/30b2b324b93ff31a0bab539c102aae59e84e444031b2742150a7646aa1bb/propcache-0.5.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:48cb48c5346a97de792254af77715aa2529c2a1ebc5f586aa0aae44a02f1fe57", size = 280500, upload-time = "2026-09-16T00:15:59.487Z" }, + { url = "https://files.pythonhosted.org/packages/64/36/721bb59f682ff060d0c8df64274fca8cd0521b1a54506c2eedaef795b7f5/propcache-0.5.4-cp314-cp314t-win32.whl", hash = "sha256:03b229037d25b801e7af53fd52b9fc49d9439b036fca1e087e02780631adfa97", size = 46121, upload-time = "2026-09-16T00:16:01.349Z" }, + { url = "https://files.pythonhosted.org/packages/c1/86/0b1b80fa1ac3a0aac44e2922a6964fbe9cd52af5eab8fa933bf9e90b030c/propcache-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:8a1fc236528c457cd739c88abe823da851b7ab645d72792f88658114cc340c12", size = 49154, upload-time = "2026-09-16T00:16:02.901Z" }, + { url = "https://files.pythonhosted.org/packages/69/4f/9fe6f05a47cb550c823155052116f710064b6be5c6e8ec4e9faae7e18115/propcache-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:135036c5cfc93864affb0f9af9a27e5d7a71cb7bd745e7b6dbfc2d56cc30e827", size = 46005, upload-time = "2026-09-16T00:16:04.266Z" }, + { url = "https://files.pythonhosted.org/packages/f5/cd/785c64ed382f3f04201870267b02783f63b4678c2acfddc177a3ebcc2727/propcache-0.5.4-py3-none-any.whl", hash = "sha256:62c60aec739ed00124573cce1178138fd690c7676352d67a37328c1cf51d7468", size = 16338, upload-time = "2026-09-16T00:17:13.106Z" }, +] + +[[package]] +name = "pydantic" +version = "2.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/0b/8e10b2e693af8ec54346a14caf36221334775975a747c9ceaa3f8371d96d/pydantic-2.14.1.tar.gz", hash = "sha256:94f478203dd03404682a1ada216965651dd74b1d2d5ffd62e00e0837caab5c26", size = 900362, upload-time = "2026-10-11T18:37:55.396Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/ea/a56b9fe5066f3537b7882f77e9c5dfb26d8c2eefdaed9b5fc73d57b4dc22/pydantic-2.14.1-py3-none-any.whl", hash = "sha256:9195d967ec791692a04438115466764fb8b9a27b31f14a760437694f40d6b454", size = 488106, upload-time = "2026-10-11T18:37:53.437Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.50.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/24/af4ec4be49fbc810f35b0bdcacc3d433b56bbfa469fd3bfd4ae116cd9bf1/pydantic_core-2.50.1.tar.gz", hash = "sha256:e50d7b94baac6c7d09927fa5ca5800a0c7ee5015c7fcff65beb3a1931b5a6e09", size = 513515, upload-time = "2026-10-11T18:35:44.82Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/33/28b96e81677153715e3eafb9f26663a80841e859fde282a380359b0d3fa1/pydantic_core-2.50.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1cf41f1ae3fa155cf167a72689ad044bcc1e3c97e064123677149bdfb5dafc4a", size = 2224428, upload-time = "2026-10-11T18:33:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/94/40/15c06410c9b7b8da5805d27b64e09bd3f900e986728106db2689dbc51513/pydantic_core-2.50.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4df197990c15b5a37c5a277d131d9f2c67de6133f2e5dafd80d9bba4b99f46f9", size = 2023740, upload-time = "2026-10-11T18:33:08.763Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4e/5eb629f6efc2a27e421d789dd4bfacbb5f6d09c80c28b13d1e87d73163d5/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0036473f5583e6a60e50b8b21651511564277a3f05cc5dab8cf579f552cd5f6c", size = 2049435, upload-time = "2026-10-11T18:33:10.366Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8e/f195aebec49ad12318876ac2368c197a7939f5d52f8e156d2238ef8a4588/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:992c3514ec891fa7858099183e4d64e6bd5a5d4ff452fae29df22faa77a006bb", size = 2172975, upload-time = "2026-10-11T18:33:12.368Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f5/7ad9fb83010cd5ea0948409db105676ed779c4e709e2d3d89b9fe2558794/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:739dc730e6be3bd5ec2f4ab5cfc7eb047cc45fc1497b3bafec74ff2ed07df597", size = 2364197, upload-time = "2026-10-11T18:33:14.244Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fb/bf0aab3e78301d202b82a0322ec968cd703b11fc0623fde9a28708936f62/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fad3a91e51b6d2039c572db04a5a873260b399f6bd62c3552671fa7a4a2899", size = 2444259, upload-time = "2026-10-11T18:33:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/38f6d6564fae57d9b12e5676dfa947e0e7d5c46dbeaa7eb3352261d6d299/pydantic_core-2.50.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42b54c2c90ad348b5e3a85e03e715d572c1fde357ef104cdfe3b03b697a404ea", size = 2159857, upload-time = "2026-10-11T18:33:17.51Z" }, + { url = "https://files.pythonhosted.org/packages/65/a0/fb0a3ca10f139dcf765b2d312d10cf0f65c57c59993229d00ad12b14ceff/pydantic_core-2.50.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:2df1ff41884de2bc4b307bafd7c40a691094fad2ff8e767e5b45a319257bcf4e", size = 2233954, upload-time = "2026-10-11T18:33:19.591Z" }, + { url = "https://files.pythonhosted.org/packages/8c/6a/e63842252702aa4ec6e6b7178ca85e541a77459076592c23ebe2d9840b33/pydantic_core-2.50.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe90228920fd8ff2be62622b6bb8a2b11acd65046d50c6b130614b5879605a20", size = 2243876, upload-time = "2026-10-11T18:33:21.393Z" }, + { url = "https://files.pythonhosted.org/packages/39/25/5991cf8318b37e0dfab47b87541619a1df8501a793cba0d978846cba37a7/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:844b869f118e22a41a091bdcedda8a71bc1b0f62c38d1a0c3211cece47e1d8fc", size = 2277908, upload-time = "2026-10-11T18:33:23.324Z" }, + { url = "https://files.pythonhosted.org/packages/a7/3e/3ee8baaa6cc25a6961c69168cf9ff0f002d56f4e0d541ad6724c18fb61f3/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:2eb75304506894a281d346220a4f7481a1b8729577c5ed2a05395991966a8396", size = 2449285, upload-time = "2026-10-11T18:33:24.942Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/acbec6deac13fe697a80c275a9b6661db62c4d323bac8a47347b1f39c7cd/pydantic_core-2.50.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6b20a4bffabdad0db2927ac034ae3b8a681b1f7a0182f3e60b479ad2fde21ebb", size = 2479013, upload-time = "2026-10-11T18:33:26.925Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/21a3f237b264389f6219d053ee78fc1b5c2fd402c1b1cb2b6cb8b85f9834/pydantic_core-2.50.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:99ba9bc2b8062ea0c326a990f7f00e6530c23579de66dd246e72c4cafef950a5", size = 1484400, upload-time = "2026-10-11T18:33:28.693Z" }, + { url = "https://files.pythonhosted.org/packages/09/ce/077a6d262d12ef09108377ac0717f029f420d773ace63cafd6143af75568/pydantic_core-2.50.1-cp314-cp314-win32.whl", hash = "sha256:cf356f70551d40374eaffb1aa63f1eb6d2006681cbd7a9faea173ce0f4dd7cd2", size = 2103222, upload-time = "2026-10-11T18:33:30.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/4c/350a2415209c43d670eb71d3c040f332c04a30583d39e311b05c7ac15762/pydantic_core-2.50.1-cp314-cp314-win_amd64.whl", hash = "sha256:d32f3acc081cc3923386d88f422cde8892335e95f034e0104bb4cf9310d9915f", size = 2171586, upload-time = "2026-10-11T18:33:32.139Z" }, + { url = "https://files.pythonhosted.org/packages/bf/92/9bea6ca96580a0902fed366f064f0829e404f41889b34543279a1162b888/pydantic_core-2.50.1-cp314-cp314-win_arm64.whl", hash = "sha256:bed5163e03b98bc1fa2eb05d74c63d9c5c95d8ed6254985481640fbf5e237dea", size = 2155773, upload-time = "2026-10-11T18:33:33.896Z" }, + { url = "https://files.pythonhosted.org/packages/86/8c/f121f073cf32bdd5cba7e6230b1ce0ac845b0cf43e44dd597d95af272db2/pydantic_core-2.50.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:9572c1369e9c9da2d64a7b7992c786d90ff295abc93964cfe3125e4290768070", size = 2234017, upload-time = "2026-10-11T18:33:35.588Z" }, + { url = "https://files.pythonhosted.org/packages/a4/69/2bb2bcd6146cffe98d760a46c42ae71efd5151d9b2f9c9bf6619a3b32083/pydantic_core-2.50.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2005207aafe1231315718bf6ed5d064a7300fb4772754af35ee72fc68159492e", size = 2001016, upload-time = "2026-10-11T18:33:37.57Z" }, + { url = "https://files.pythonhosted.org/packages/20/b3/fbf854c7d07ec114260c26e9e2071a4381740f9ae09641dbfcbdf2a18c45/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f6047f62a6c5ae08d0a6afb035667aa2d97c3d20d69762e034c5ea144d92a5", size = 2023015, upload-time = "2026-10-11T18:33:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/65/20/6de55b2f92cdb614b745c6e9ced639fc4fd7e1e77604825a88bdece6fcbd/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ef800dd7d85bcdadf4c3076e4c94e43939493558a3b69a1ea830c706d4617bb", size = 2192543, upload-time = "2026-10-11T18:33:41.381Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d9/19e91c94bd5c405945ce2f15526808aea37e162c253160de8ed7bf70b406/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0135bcdcaa0f23573f286e4cb5e0fd2962700964ed13df085b85f2b97aeab9e", size = 2388704, upload-time = "2026-10-11T18:33:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/b6/bc/2e24c8415eae1a25ee5a5484946a917123bad2e9d01689a759236928175a/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b3a6f334c6a2345ca15318ff894502a90012536404b37c844a976c76c846e0b", size = 2464118, upload-time = "2026-10-11T18:33:44.856Z" }, + { url = "https://files.pythonhosted.org/packages/bd/1f/945b8053cb061c64e102bcaf7bfb9ed740c0bd4349f9bb978a7d4ddff4ab/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06e01fbbfdb9be777b316a71b6c49efaf4a08b615d0a98d678cda3023f79d019", size = 2150142, upload-time = "2026-10-11T18:33:46.661Z" }, + { url = "https://files.pythonhosted.org/packages/2b/78/96a3e50bf0d64aaae781107eb9335b7529d05a85793ed6e7241c4cd1d931/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a29a061fec0b4e2d714f277e70a3a18125ecff803f2fea6eade2f2e53711d112", size = 2245577, upload-time = "2026-10-11T18:33:48.574Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5d/a6038a0322232758a6ebfa709f4ca14a60bf5b93940cd0b345056a557da4/pydantic_core-2.50.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f5187624823423e1d1b82b1072ac41dc837389e18d3d0572cc19bbee46cd550a", size = 2263728, upload-time = "2026-10-11T18:33:50.527Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4b/76ded3333a457a9344c4f2d63f2d82d0101654b05178fa4ddfe7d3627674/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3e46a9eb0a0901dd6275e6b06ac3a464885ef350ec4121fe486869de8053e4bb", size = 2293630, upload-time = "2026-10-11T18:33:52.362Z" }, + { url = "https://files.pythonhosted.org/packages/a9/00/9eca378335c9c1b72bc779bf6e4c2a82ce784f14ec48a0e87102c9870e05/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:756d669f04e62ec4148ecfe22be6a4484d9b1181a6ef32e205ebfd200540858b", size = 2468048, upload-time = "2026-10-11T18:33:54.118Z" }, + { url = "https://files.pythonhosted.org/packages/35/ca/e3832e9cf93651251de43c8c5c029ed680a3c1a0a1b17ee26c81bed08cbd/pydantic_core-2.50.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:c516cc5367ca3448995d42cb994bf3f4c9002d2a7c22eac9622551269ad1b807", size = 2512159, upload-time = "2026-10-11T18:33:55.99Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f2/773469b5a10a39116a2cb17edaf6d722f017dd8da07161b371465311c542/pydantic_core-2.50.1-cp314-cp314t-win32.whl", hash = "sha256:9d1bed94af6a63835461f3cf7502058eb166c58c4778e11d0f433cfb1bd69e19", size = 2098406, upload-time = "2026-10-11T18:33:58.043Z" }, + { url = "https://files.pythonhosted.org/packages/ee/42/0bb74f8f25204b259b11ab7c12dc7f180893b6bd706118b385147fb6efd5/pydantic_core-2.50.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c8dce1f1e0e5358b682a6ad3fa5e31b31d4560997b8e61417e9217c8d60f8a0c", size = 2148481, upload-time = "2026-10-11T18:33:59.913Z" }, + { url = "https://files.pythonhosted.org/packages/47/0d/d801646c9679a4e630e15cf521d4108b93854b42a6e6e02391bd4c6b1095/pydantic_core-2.50.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ceff0acc940be2715bd6ad17b24c0e5304abf44f6efd0f81ee8499e640f9dc86", size = 2174972, upload-time = "2026-10-11T18:34:01.875Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +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" } +wheels = [ + { 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" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[package]] +name = "pytest-timeout" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[[package]] +name = "pytokens" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, +] + +[[package]] +name = "ruff" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/94/1e5e4967626faf12fa56999cd6222dff6992ceb086ad7945756baf70c7a7/ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982", size = 4790557, upload-time = "2026-07-23T19:11:30.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/81/1c8818fee7ce1a04cd7d1b3172e0a8f8e4f1dc4feb7fc390e16daa8af323/ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e", size = 10754633, upload-time = "2026-07-23T19:10:46.415Z" }, + { url = "https://files.pythonhosted.org/packages/23/df/beaf59c09d68db84304d555f188b276a77132a5d5b0b67a5c762aa143628/ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522", size = 10969164, upload-time = "2026-07-23T19:10:50.271Z" }, + { url = "https://files.pythonhosted.org/packages/42/ce/741cd197496a1abbf51352710fd15ed995d2a2be87189c1da26a450d6e83/ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81", size = 10488846, upload-time = "2026-07-23T19:10:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/a2db8e88cade358f5cdcb05674a917751074109315d014eb6352d9a893f7/ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9", size = 10889729, upload-time = "2026-07-23T19:10:54.89Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/62a771694ebd63029dc953e27dbad40e1588bd4860ff9fe881018fddaa49/ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a", size = 10568275, upload-time = "2026-07-23T19:10:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e2/ced249fe8af5f086c5c58cc21cc3356d50f32f7401c5df87050c999620a7/ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717", size = 11385112, upload-time = "2026-07-23T19:10:59.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/05154977a8fd69eeb6c103271f55403bfd8711f5c0f8ed07489d95a504e7/ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17", size = 12207008, upload-time = "2026-07-23T19:11:02.154Z" }, + { url = "https://files.pythonhosted.org/packages/fb/29/98225831a3a1eab0e02f4acc6ca6559a98611dcc68b6965ff4b7234627c1/ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4", size = 11650842, upload-time = "2026-07-23T19:11:04.557Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/6bd3cf90500653d55dc0ffc8507aa8300bd49d0214b2e8cb4d3fef2943ba/ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b", size = 11400718, upload-time = "2026-07-23T19:11:09.233Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a2/a54eb4eae05d66364050a5d3b8a9c5ef88196531b3cbe7109d873f87f819/ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0", size = 11426177, upload-time = "2026-07-23T19:11:11.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/16e3eea4b2a478a496919f5e36f17c4559e54620bd3bbac5d6affa068006/ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213", size = 10856126, upload-time = "2026-07-23T19:11:14.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/252eb8b868a16eec7257c14f504f77537e734b2d69c762e639e588e304a3/ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af", size = 10571208, upload-time = "2026-07-23T19:11:16.378Z" }, + { url = "https://files.pythonhosted.org/packages/21/09/817a482f542f7570cbb4554b26e896610c7114f539b1d9e2d2145bf6bef6/ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09", size = 11063329, upload-time = "2026-07-23T19:11:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/2e/23/9403c180ca1cb9b1f7335f5c3e5305c09d49ea5b345196682a36028bde4a/ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed", size = 11489751, upload-time = "2026-07-23T19:11:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/1b2ef7bcde851c78d7f17f1cca13fd6dc695fc4b3d6197941e72cae5b132/ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb", size = 10785885, upload-time = "2026-07-23T19:11:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/d5e4ef7a56be3f928ffb90b94c25ba7d3cb9c7fe0736aeaaedf361770712/ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472", size = 11923141, upload-time = "2026-07-23T19:11:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9a/8415f2657cbe200f41a4531ccededf135505a92d4a012229121f885b26f9/ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d", size = 11273407, upload-time = "2026-07-23T19:11:28.705Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, +] + +[[package]] +name = "yarl" +version = "1.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/16/e8be8e2fb175bbf41a0680381a319f1199fae256588241a2ac8677eafb49/yarl-1.25.1.tar.gz", hash = "sha256:03dd38de09bc213e9a8b29761eec33ee1d5318dac0e49d8af36e4d27830e23a7", size = 246245, upload-time = "2026-09-15T19:35:02.264Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/cf/54023edfab7aa773b860503db0c56e962ccab0922803ee97988c176ea090/yarl-1.25.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a9ca696eb02e5c02a8afd872ada510eba9b7fe6e68b9572c2e9a9b1941e31e2e", size = 143975, upload-time = "2026-09-15T19:32:16.416Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a8/e6c1be0e6761d0f2d10bbf33a3e1e02b99dc83874d92945d7b461a72481e/yarl-1.25.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a5877f2255aab518ebe528289037699201d5dc5f045f2396cb30aa02db22f57f", size = 104018, upload-time = "2026-09-15T19:32:18.364Z" }, + { url = "https://files.pythonhosted.org/packages/6e/bb/dda344765ffd3430afe1a1c66c866a57fae67786537d4f14607df6505ac1/yarl-1.25.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7a5c3115595995779ee21f2567035793911c3802a43c74f3fbb0314929ec67ac", size = 104156, upload-time = "2026-09-15T19:32:20.459Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5f/ed1538bcd06009fe990d6d283dd7667f639e62a81e35c6d8c6ef6c08fb3c/yarl-1.25.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77e5099b99b37f3cf79c246998ca9f7313a78054cd1809ec46bc1afad47e1c4c", size = 116025, upload-time = "2026-09-15T19:32:22.766Z" }, + { url = "https://files.pythonhosted.org/packages/a2/af/2185daf56b99830d3356ecfada46faaa49945de6626e842b7728088d4980/yarl-1.25.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6efaf45df6a849cef613a03a94c845647456662f85438c886bb67a9c027c8c2c", size = 106985, upload-time = "2026-09-15T19:32:24.749Z" }, + { url = "https://files.pythonhosted.org/packages/c1/65/bc1ae564fb4b04a30b6a8f250e787772581c57e4c3d5cf07ac3359de3103/yarl-1.25.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5f90e44653c4e0f78501ed9bb7d3fce835a8d62b7c6ed0cb16557534087e743", size = 123030, upload-time = "2026-09-15T19:32:27.084Z" }, + { url = "https://files.pythonhosted.org/packages/6a/3e/e2afcde10d74e53b3fa889960991efb3019beda2b1682a01de720a302056/yarl-1.25.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:632da579b2d879f6bad20f2cfa35ded1efe2f4f77f8abb26a6234a5b236acd2f", size = 126765, upload-time = "2026-09-15T19:32:29.332Z" }, + { url = "https://files.pythonhosted.org/packages/a2/be/415b00c0fe5a0615b062a456b26623d7ec91c2bee20faea1a14045aa0469/yarl-1.25.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:30eec96e8a91bd588ce897c9543f6d5d8d34b28fbcba28a4dedf20ebeae9fe57", size = 117199, upload-time = "2026-09-15T19:32:31.49Z" }, + { url = "https://files.pythonhosted.org/packages/97/27/3d8c63ddd3e8bcfd033748ab93876678ce59bacd66e4cb1ed851c9c5b37e/yarl-1.25.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:12b6bc4906e11f5e1a1cdcb12296e7afbd366c783cc8073403cd2fb74334e453", size = 115187, upload-time = "2026-09-15T19:32:34.137Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/7a81d0be1a502a26a0d4326c6f2ecb736c824f570ea1c6529f2b0b227b50/yarl-1.25.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9d6ed3d17bccce4c05343e1ca8da13bc5c02c812a4e7282ddd05e8769322d3fc", size = 116085, upload-time = "2026-09-15T19:32:36.438Z" }, + { url = "https://files.pythonhosted.org/packages/f0/69/39fff459916aa0fab42215dc47b759586fd80f94aa56dfc4a7c15ba6e0dc/yarl-1.25.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:f38a70074041d3b7e138e452799f5174198bae5bd5ab2000917badf403908c5f", size = 107996, upload-time = "2026-09-15T19:32:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/c0/39/80b9a55a3335590451d9ecf3eb593a8c635351f4c905ef056d7e8a8fd9e7/yarl-1.25.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca89e4e21854ed27ec753297dde84b16c9f8e53b14a4866fb44457d643c19f8", size = 122549, upload-time = "2026-09-15T19:32:41.151Z" }, + { url = "https://files.pythonhosted.org/packages/42/7d/a179c6757818bb59372a4adafd09f7f26a3b4a0f04c3ae404b544c0b0c82/yarl-1.25.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1ab7618921a93767387a4b83776f751588f5b5ae9bb5bc96620e2e2e00bca868", size = 115107, upload-time = "2026-09-15T19:32:43.072Z" }, + { url = "https://files.pythonhosted.org/packages/32/2b/a773ac867e4ab53a98ed98e5cefe3bae31e6f550252ca9d1de266f1a40c5/yarl-1.25.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0ae12ff2b805fa02c4dab838005caef735e39986322698c48588d3beacb65c62", size = 120666, upload-time = "2026-09-15T19:32:45.061Z" }, + { url = "https://files.pythonhosted.org/packages/bc/41/52be6505e85b0f76b4f85b01b5de7e06a0512201abc2c95e14e099549174/yarl-1.25.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90c30ed53546da833c700115c0064c22120d1b1560f474699fd31f22dd668233", size = 117505, upload-time = "2026-09-15T19:32:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/f5/01/349c0386caedbbe488d519f252df54efac8a1459282d466c474bdd84a620/yarl-1.25.1-cp314-cp314-win_amd64.whl", hash = "sha256:acfa7e22aa6c6e7a5996a41d275bfa01efa7ea56ab890590280e9063e2cf5c1b", size = 103446, upload-time = "2026-09-15T19:32:49.615Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f0/8ec63180f77912f0dc4e5a42760cb8c08d20da1d5ace3578a01b84d1f3d8/yarl-1.25.1-cp314-cp314-win_arm64.whl", hash = "sha256:8e7d98cdbb6d71e726f7d525952867096053d1f290dd4e3c50d7d313a136f414", size = 99159, upload-time = "2026-09-15T19:32:51.686Z" }, + { url = "https://files.pythonhosted.org/packages/47/7d/92d2220d6886b70ab1ed8579533ac2af2dfac716d5d929001daff7986df9/yarl-1.25.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d21f0fa80a02d05299207eeaafef345d812ace96d5306e4ef265e1d419a615fa", size = 150071, upload-time = "2026-09-15T19:32:53.911Z" }, + { url = "https://files.pythonhosted.org/packages/64/fc/b245e448124bcda9340df38e3553fa222b50260fca027a84095e9bd8642d/yarl-1.25.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17c9877a89fb6e2bca6f9087eb24cd7fb434653946ef5075e470d23d49b52287", size = 106780, upload-time = "2026-09-15T19:32:56.443Z" }, + { url = "https://files.pythonhosted.org/packages/51/e2/9a6ce2e334ebf218a30335ae76fb1696459430d42f733b8cb0d7d65b84d3/yarl-1.25.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:29273edf1530e397bd07cb784db1fbe0d2590b77569f2e24679a9c0a2d763b94", size = 107361, upload-time = "2026-09-15T19:32:58.827Z" }, + { url = "https://files.pythonhosted.org/packages/ed/70/66e8c76b569b450d16e190f15071c916c3df70b0e33927e415ac497cf0c2/yarl-1.25.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7abffdf37af1cec6a2ad69b827aa84320db5894791bc8ed932dc93fb274b7e9", size = 114396, upload-time = "2026-09-15T19:33:02.24Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/0d82838a05c57fdc05bc8b66e8c92dcc0df15e27463a5f163142d521c682/yarl-1.25.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2239a02249d9326655419e0168a28ca9008938eaab31dc29fc875c217927a6c0", size = 104882, upload-time = "2026-09-15T19:33:04.494Z" }, + { url = "https://files.pythonhosted.org/packages/86/d4/ea08615c4edaa6049a13a2f1128944d068d1893abda7d708d4d7ea01599a/yarl-1.25.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:664ec6a520b74a1df2810666eb67695fcb77fa663e6ea0a25aaf2e529cb24dfa", size = 119485, upload-time = "2026-09-15T19:33:06.583Z" }, + { url = "https://files.pythonhosted.org/packages/1a/82/0898bdce9b1ae403b308b9c733d0d24af4a3464270c2c081f457b16c3e0d/yarl-1.25.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4f1c91f5a5980a937ff8e238e98e6897e1ad74a4b1e2c0d68c73b5ffbb3f5c0b", size = 122490, upload-time = "2026-09-15T19:33:08.653Z" }, + { url = "https://files.pythonhosted.org/packages/d1/38/97d79b81c342b78246cfedb74809e68841f3198d21653e10d3232bd9c622/yarl-1.25.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c88edaec8c349ad4c5ad4c486a3defcc4b80ceb2f074436ffa0a87caf5e76a6", size = 115336, upload-time = "2026-09-15T19:33:11.056Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9d/2577896554cd310dc470adb6da0b7dd0b435cb63e2565204a7ac240e504c/yarl-1.25.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:35dcbea443fafb3eece757ad4e514560ddeb6c34cfae1582c620d7b293d7feee", size = 111825, upload-time = "2026-09-15T19:33:13.204Z" }, + { url = "https://files.pythonhosted.org/packages/29/6b/7ac49d8ba84a5c4bd73415a4c949d22c749cb3762579b3d50e48019a78aa/yarl-1.25.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:882569ff613758cac762a457a5d72d6e211b28d4bcfea89d1d71ea942b02eac0", size = 114655, upload-time = "2026-09-15T19:33:15.553Z" }, + { url = "https://files.pythonhosted.org/packages/e5/18/e5942a16723f5b72f9b1297fd5a85a54f6300cd15c0dcb5005b90cd89156/yarl-1.25.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d0f1489233a254bb3643d2f05de7d59019254d81daeca6b9162fe9edef57e0c7", size = 106395, upload-time = "2026-09-15T19:33:17.599Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2d/549fa46240781513ebc47ae7eb418df428a163a2a3d644cc9cbb3ecb7846/yarl-1.25.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f41753a76f4f63927d03a0d8ba8f5ce0f2083bec29a8cfaccc55371b1564b96b", size = 119277, upload-time = "2026-09-15T19:33:19.973Z" }, + { url = "https://files.pythonhosted.org/packages/76/16/4763f78dcdc0b3b9fb3842b04afe72b9320857c6a69300c62a0eab03d119/yarl-1.25.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fb0eb4955adf0579001581f2f71a126e8781ba61bcd120f127b0401163c6c2d", size = 112504, upload-time = "2026-09-15T19:33:22.464Z" }, + { url = "https://files.pythonhosted.org/packages/ae/b4/974e3edfe0d188393ce1cb9de400111c63fe61f4eb3b772a500d84c970d1/yarl-1.25.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a1e32763e641a1566507d90a8d3b19bfc3cc04a9d4e5ae3e32189874ed4b58a3", size = 116243, upload-time = "2026-09-15T19:33:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/b0/aa/157b940428da80c104ca09666a740e51c94963df65d5b112e06b52e4d7a8/yarl-1.25.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65b5b2066651b7432d389e9799d979c703bcc6ef44266bb8153ef54e91e4aab3", size = 115822, upload-time = "2026-09-15T19:33:26.886Z" }, + { url = "https://files.pythonhosted.org/packages/7e/af/19fbdce41412e1b96825544cc52cd7029d3724655d0988237972f078bd29/yarl-1.25.1-cp314-cp314t-win_amd64.whl", hash = "sha256:734f6e5400352ac4254456003d462866c684703570929cff7a7bde015d0cb371", size = 107386, upload-time = "2026-09-15T19:33:29.009Z" }, + { url = "https://files.pythonhosted.org/packages/2a/99/f6431c8968e89be608d74b28ae2d024521b2953f27dd44e0dece5e04f67a/yarl-1.25.1-cp314-cp314t-win_arm64.whl", hash = "sha256:287e99ff5aa4dc1c7630bfc683ded6f106d756c99dec432a2d7f197a784f51c6", size = 102094, upload-time = "2026-09-15T19:33:31.151Z" }, + { url = "https://files.pythonhosted.org/packages/54/22/318c7980066769c6bcd9221ed2248294f5698811da099013098c670565ed/yarl-1.25.1-py3-none-any.whl", hash = "sha256:681c758b0490f9e96b78e5fa8e8dc6e648e9185bb6eaebe73183c33ea0c445f3", size = 63617, upload-time = "2026-09-15T19:34:59.616Z" }, +]