diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..95b9560 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,27 @@ +## Problem and change + +Describe the problem and how the change fixes it. Include compatibility or +migration details when relevant. + +## Validation + +List commands actually run and their results. For changed nontrivial behavior, +identify the distinct cases covered; add rows or explain why a category does not +apply. Include further branches and faults where the contract requires them. + +| Behavior | Success | Invalid input | Boundary | Failure / recovery | +| --- | --- | --- | --- | --- | +| Describe the behavior | Test or evidence | Test or reason | Test or reason | Test or reason | + +Name any unperformed checks and remaining uncertainty. Include UI screenshots or +hardware evidence when applicable. + +## When relevant + +Remove items that do not apply and complete the relevant ones: + +- New package, dependency, automation, or doc: why it is needed and how it stays current. +- Dependency update: verified stable version, source, compatibility, and lockfile. +- Package release: changeset and consumer impact, or why no release is needed. +- Brand change: package version, exported assets/tokens, and visual checks. +- Equipment behavior: safety-change record, fault evidence, and reviewer. diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b7c37c9..e93fe03 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -22,5 +22,8 @@ jobs: - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.12" - - run: pip install gerbonara shapely - - run: python tools/validate_gerbers.py board/gerber-rules.json "board/build/2026-09-09/gerber.zip" + - uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3 + with: + just-version: '1.58.0' + - run: python -m pip install --require-hashes -r requirements-dev.txt + - run: just check diff --git a/.gitignore b/.gitignore index b908d4c..c922b93 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ __pycache__/ *.pyc .DS_Store + +/.origin89/engineering/ +/.agents/skills/origin89-* +/.venv/ diff --git a/.origin89/sync-engineering.py b/.origin89/sync-engineering.py new file mode 100644 index 0000000..3a86ce9 --- /dev/null +++ b/.origin89/sync-engineering.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 +"""Refresh Origin89 skills from the public engineering repo into an ignored cache.""" + +import argparse +import hashlib +import io +import json +import os +from pathlib import Path, PurePosixPath +import re +import tarfile +import tempfile +import urllib.error +import urllib.request + +SOURCE = "origin89hq/engineering" +HEAD_URL = f"https://api.github.com/repos/{SOURCE}/commits/main" +MAX_DOWNLOAD = 2 * 1024 * 1024 +MAX_CONTENT = 4 * 1024 * 1024 +SHA = re.compile(r"[0-9a-f]{40}") +NAME = re.compile(r"origin89-[a-z0-9]+(?:-[a-z0-9]+)*") + + +def download(url): + request = urllib.request.Request(url, headers={"User-Agent": "origin89-skills-sync/1"}) + with urllib.request.urlopen(request, timeout=10) as response: + data = response.read(MAX_DOWNLOAD + 1) + if len(data) > MAX_DOWNLOAD: + raise ValueError("Engineering download exceeds the 2 MiB limit") + return data + + +def skill_files(archive): + """Read only regular files under namespaced skill folders; never extract tar paths.""" + files = {} + total = 0 + entries = 0 + with tarfile.open(fileobj=io.BytesIO(archive), mode="r:gz") as bundle: + for entry in bundle: + entries += 1 + total += entry.size + if entries > 1024 or total > MAX_CONTENT or entry.size < 0: + raise ValueError("Engineering archive exceeds its size or entry limit") + parts = PurePosixPath(entry.name).parts + if len(parts) < 3 or parts[1] != "skills": + continue + if entry.name.startswith("/") or "\\" in entry.name or ".." in parts: + raise ValueError("Unsafe path in skill archive") + if entry.isdir(): + continue + if len(parts) < 4 or not NAME.fullmatch(parts[2]) or not entry.isfile(): + raise ValueError("Only regular files in origin89-* skill folders are allowed") + relative = Path(*parts[1:]) + if relative in files or entry.size > 256 * 1024: + raise ValueError("Duplicate or oversized skill file") + if len(files) >= 256: + raise ValueError("Skill archive exceeds its file or content limit") + data = bundle.extractfile(entry).read() + data.decode("utf-8") + files[relative] = data + names = {path.parts[1] for path in files} + if not {"origin89-working", "origin89-writing", "origin89-commits"} <= names: + raise ValueError("Archive is missing the shared working, writing, or commit skill") + for name in names: + entry = files.get(Path("skills") / name / "SKILL.md", b"").decode() + sections = entry.split("---", 2) + if len(sections) != 3 or sections[0].strip(): + raise ValueError(f"Missing skill frontmatter: {name}") + header = sections[1].splitlines() + if f"name: {name}" not in header or not any( + line.startswith("description:") and line.partition(":")[2].strip() + for line in header + ): + raise ValueError(f"Invalid skill name or description: {name}") + return files, sorted(names) + + +def directory(path): + """Reject redirected cache/discovery directories before creating children.""" + if path.is_symlink() or (path.exists() and not path.is_dir()): + raise ValueError(f"Expected a normal directory: {path}") + path.mkdir(exist_ok=True) + + +def snapshot_state(snapshot, revision): + if snapshot.is_symlink() or not snapshot.is_dir(): + raise ValueError("Expected an immutable snapshot directory") + state_path = snapshot / "state.json" + if state_path.is_symlink() or state_path.stat().st_size > 64 * 1024: + raise ValueError("Invalid cached state file") + state = json.loads(state_path.read_text()) + if not isinstance(state, dict): + raise ValueError("Invalid cached state") + if state.get("source") != SOURCE or state.get("revision") != revision: + raise ValueError("Invalid cached source or revision") + names = state.get("skills") + if not isinstance(names, list) or not names or any(not isinstance(n, str) for n in names): + raise ValueError("Invalid cached skill list") + if len(names) != len(set(names)): + raise ValueError("Duplicate cached skill names") + for name in names: + if not isinstance(name, str) or not NAME.fullmatch(name): + raise ValueError("Invalid cached skill name") + if not (snapshot / "skills" / name / "SKILL.md").is_file(): + raise ValueError(f"Missing cached skill: {name}") + hashes = state.get("files") + if not isinstance(hashes, dict) or not hashes or len(hashes) > 256: + raise ValueError("Missing cached file hashes") + actual = set() + total = 0 + for path in snapshot.rglob("*"): + if path.is_symlink(): + raise ValueError("Symlink inside cached snapshot") + if path.is_file() and path != state_path: + relative = path.relative_to(snapshot).as_posix() + total += path.stat().st_size + if path.stat().st_size > 256 * 1024 or total > MAX_CONTENT: + raise ValueError("Oversized cached skill file") + if hashes.get(relative) != hashlib.sha256(path.read_bytes()).hexdigest(): + raise ValueError(f"Cached skill was modified: {relative}") + actual.add(relative) + if actual != set(hashes): + raise ValueError("Cached files do not match their manifest") + return state + + +def current_state(cache): + pointer = cache / "current" + if not pointer.is_symlink(): + if pointer.exists(): + raise ValueError("Managed current pointer is not a symlink") + return None + target = os.readlink(pointer) + revision = Path(target).name + if not SHA.fullmatch(revision) or target != f"versions/{revision}": + raise ValueError("Unexpected managed cache pointer") + versions = cache / "versions" + if versions.is_symlink(): + raise ValueError("Redirected snapshot directory") + return snapshot_state(versions / revision, revision) + + +def activate(project, cache, state, previous): + directory(project / ".agents") + discovery = project / ".agents" / "skills" + directory(discovery) + old_names = set(previous["skills"]) if previous else set() + new_names = set(state["skills"]) + targets = {} + for name in old_names | new_names: + link = discovery / name + target = os.path.relpath(cache / "current" / "skills" / name, discovery) + if link.is_symlink(): + if os.readlink(link) != target: + raise ValueError(f"Refusing to replace a custom skill link: {link}") + elif link.exists(): + raise ValueError(f"Refusing to replace local skill files: {link}") + targets[name] = target + next_pointer = cache / "next" + if next_pointer.exists() or next_pointer.is_symlink(): + raise ValueError("Unexpected pending cache pointer; inspect it before retrying") + for name in sorted(new_names): + link = discovery / name + if not link.is_symlink(): + link.symlink_to(targets[name], target_is_directory=True) + next_pointer.symlink_to(f"versions/{state['revision']}", target_is_directory=True) + os.replace(next_pointer, cache / "current") + for name in sorted(old_names - new_names): + link = discovery / name + if link.is_symlink(): + link.unlink() + + +def refresh(project, fetch=download, offline=False): + project = Path(project).resolve(strict=True) + directory(project / ".origin89") + cache = project / ".origin89" / "engineering" + directory(cache) + lock = cache / "sync.lock" + try: + lock.mkdir() + except FileExistsError as error: + raise ValueError("Another refresh is active; inspect sync.lock if it was interrupted") from error + try: + previous = current_state(cache) + reason = "offline requested" if offline else None + state = None + if not offline: + try: + metadata = json.loads(fetch(HEAD_URL)) + revision = metadata.get("sha") if isinstance(metadata, dict) else None + if not isinstance(revision, str) or not SHA.fullmatch(revision): + raise ValueError("GitHub did not return a valid engineering revision") + if previous and previous["revision"] == revision: + state = previous + else: + archive = fetch(f"https://codeload.github.com/{SOURCE}/tar.gz/{revision}") + except (urllib.error.URLError, TimeoutError, ConnectionError) as error: + reason = f"refresh unavailable: {error}" + if reason is None and state is None: + files, names = skill_files(archive) + versions = cache / "versions" + directory(versions) + target = versions / revision + state = {"source": SOURCE, "revision": revision, "skills": names, + "files": {path.as_posix(): hashlib.sha256(data).hexdigest() + for path, data in files.items()}} + with tempfile.TemporaryDirectory(prefix="prepare-", dir=cache) as scratch: + stage = Path(scratch) / "snapshot" + stage.mkdir() + for path, data in files.items(): + dest = stage / path + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_bytes(data) + (stage / "state.json").write_text(json.dumps(state, indent=2) + "\n") + if target.exists() or target.is_symlink(): + if snapshot_state(target, revision) != state: + raise ValueError("Existing snapshot does not match the upstream revision") + else: + stage.rename(target) + if reason is not None: + if previous is None: + raise ValueError(f"No cached engineering skills available ({reason})") + state = previous + activate(project, cache, state, previous) + return {key: state[key] for key in ("source", "revision", "skills")} | { + "path": str(cache / "versions" / state["revision"]), "cached": reason} + finally: + lock.rmdir() + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--project", type=Path, default=Path(__file__).resolve().parent.parent) + parser.add_argument("--offline", action="store_true", help="Use and report the existing cached revision") + args = parser.parse_args() + try: + result = refresh(args.project, offline=args.offline) + except (ValueError, OSError, tarfile.TarError) as error: + parser.exit(1, f"Engineering skills: {error}\n") + print(json.dumps(result, indent=2)) + if result["cached"]: + print("Using cached skills; this run did not confirm the latest upstream content.") + + +if __name__ == "__main__": + main() diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c794a60 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# Working in this repository + +At the start of each new task, run `just skills-sync` from the repository root. +Read `skills/origin89-working/SKILL.md` and the relevant domain skills under the +immutable `path` printed by that command. Keep that snapshot for the task; do not +refresh it halfway through work. Read local instructions and preserve stronger +project constraints and project-specific skills. + +If refresh reports cached content, continue with that verified cache and mention +that the script could not check for updates. If no cache is available or +validation fails, report the error; do not claim the shared rules loaded. Local +instructions and the user's request still apply. Do not overwrite local skill +files to fix a conflict without reconciling them. + +[Origin89 engineering](https://github.com/origin89hq/engineering) owns the shared +rules. Keep only repository-specific architecture, commands, target constraints, +and exceptions below. Internal RFCs and research belong in +[internal-research](https://github.com/origin89hq/internal-research). Add documentation +only when its value and upkeep are clear; remove AI filler from every message. + +## Board work + +Read `CONTRIBUTING.md`, `board/CAMERA-LAYOUT.md`, and the board's Gerber +rules before changing artwork. Preserve the EasyEDA sources and archived +fabrication exports. Import new exports through `tools/import_easyeda_export.py`; +validation must not modify them. Placement and routing changes include their +source scripts and the generated checker result. + +Run `just check` for the selected fabrication export. A failing copper rule is +a board finding: report it and keep the gate failing until the artwork is fixed. +Gerber validation does not establish electrical or bench qualification. This is +a Python/CAD repo; do not add Node or Cargo workspaces without actual consumers. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..cf65511 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +Read `AGENTS.md` first. Follow its task-start skill refresh and repository checks. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b706884..34076f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,3 +20,26 @@ File a fabrication export with `tools/import_easyeda_export.py board `. It dates the directory, fixes the names, and refuses to change a file that is already filed. Then run the Gerber check and put the result, pass or the rule numbers that failed, in the README. + +## Development setup + +Follow the [Origin89 engineering standards](https://github.com/origin89hq/engineering) +for working practices, tests, writing, and commits. `AGENTS.md` loads shared +skills at the start of a task; `just skills-sync` refreshes them from engineering. +Keep local constraints and domain-specific checks alongside those shared rules. + +Install just 1.58.0 and Python 3.9+ for the skill bootstrap. Run `just --list` +for repository commands and `just check` before opening a pull request. + +Create a Python 3.12 virtual environment and install `requirements-dev.txt` +with `python -m pip install --require-hashes -r requirements-dev.txt`. CI uses the same pins. + +Dependency updates start in `requirements-dev.in`; regenerate the hash-locked +`requirements-dev.txt` from the repository root: + +```sh +uv pip compile requirements-dev.in --python-version 3.12 \ + --generate-hashes --universal --no-header -o requirements-dev.txt +``` + +Update the export selected by `just gerbers` when filing a reviewed board revision. diff --git a/justfile b/justfile new file mode 100644 index 0000000..fce6fbe --- /dev/null +++ b/justfile @@ -0,0 +1,12 @@ +default: + @just --list --unsorted + +# Refresh the shared skills once at the start of a task. +skills-sync: + python3 .origin89/sync-engineering.py + +# Validate the exact filed export selected for this board. +check: gerbers + +gerbers: + python3 tools/validate_gerbers.py board/gerber-rules.json board/build/2026-09-09/gerber.zip diff --git a/requirements-dev.in b/requirements-dev.in new file mode 100644 index 0000000..7000895 --- /dev/null +++ b/requirements-dev.in @@ -0,0 +1,4 @@ +# Board validation and EasyEDA export import. +gerbonara==1.6.3 +shapely==2.1.2 +openpyxl==3.1.5 diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..8667fb6 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,322 @@ +aiofiles==25.1.0 \ + --hash=sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2 \ + --hash=sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695 + # via quart +blinker==1.9.0 \ + --hash=sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf \ + --hash=sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc + # via + # flask + # quart +click==8.5.0 \ + --hash=sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360 \ + --hash=sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34 + # via + # flask + # gerbonara + # quart +et-xmlfile==2.0.0 \ + --hash=sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa \ + --hash=sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54 + # via openpyxl +flask==3.1.3 \ + --hash=sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb \ + --hash=sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c + # via quart +gerbonara==1.6.3 \ + --hash=sha256:78848e315d456b7d01b97590f303f681232bc06d868f37a2a5f225778bf95e35 \ + --hash=sha256:fccf782acdc98e80e3a418b7ed57d35c4f6ac7a8bc5e5d3ddc48bdd870919be2 + # via -r requirements-dev.in +h11==0.16.0 \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + # via + # hypercorn + # wsproto +h2==4.4.1 \ + --hash=sha256:0e25f1462b23c9cb82d9eb02e28bc706dac2a68cb457c6a0d74d63c8a2a5d0e6 \ + --hash=sha256:4e866ffb1a869ae14dd9b5e6beb5c24a13da0495ad72b65925ded182521c1516 + # via hypercorn +hpack==4.2.0 \ + --hash=sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0 \ + --hash=sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986 + # via h2 +hypercorn==0.18.0 \ + --hash=sha256:225e268f2c1c2f28f6d8f6db8f40cb8c992963610c5725e13ccfcddccb24b1cd \ + --hash=sha256:d63267548939c46b0247dc8e5b45a9947590e35e64ee73a23c074aa3cf88e9da + # via quart +hyperframe==6.1.0 \ + --hash=sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5 \ + --hash=sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08 + # via h2 +itsdangerous==2.2.0 \ + --hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \ + --hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173 + # via + # flask + # quart +jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 + # via + # flask + # quart +markupsafe==3.0.3 \ + --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \ + --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ + --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \ + --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \ + --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \ + --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \ + --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \ + --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \ + --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \ + --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \ + --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \ + --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \ + --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \ + --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \ + --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \ + --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \ + --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \ + --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \ + --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \ + --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \ + --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \ + --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \ + --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \ + --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \ + --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \ + --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \ + --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \ + --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \ + --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \ + --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \ + --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \ + --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \ + --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \ + --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \ + --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \ + --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \ + --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \ + --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \ + --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \ + --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \ + --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \ + --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \ + --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \ + --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \ + --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \ + --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \ + --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \ + --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \ + --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \ + --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \ + --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \ + --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \ + --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \ + --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \ + --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \ + --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \ + --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \ + --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \ + --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \ + --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \ + --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \ + --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \ + --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \ + --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \ + --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \ + --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \ + --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \ + --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \ + --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \ + --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \ + --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \ + --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \ + --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \ + --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \ + --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \ + --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \ + --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \ + --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \ + --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \ + --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \ + --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \ + --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \ + --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \ + --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \ + --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \ + --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \ + --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \ + --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \ + --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50 + # via + # flask + # jinja2 + # quart + # werkzeug +numpy==2.5.3 \ + --hash=sha256:012e66aca395d795496446e52aeeb5866312a5d4d3f27da270e5a0b43f70dc5c \ + --hash=sha256:09d5a423c71ad5feb5625844ad58050e35df43871004b52ac9c0ad44a56775be \ + --hash=sha256:09ffa5d903faeaa5c4dd05009cf81c8bab9f2cb37c548b8d39b65b4cfa7c97f7 \ + --hash=sha256:0a59a421a32580a009e8a1751345bf829631b990dc1794b80514ab722b435def \ + --hash=sha256:116f96cadd935c6122e9228d676fe7ede19e741f5c8bb1c3cddbe0c51ccebea2 \ + --hash=sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0 \ + --hash=sha256:15aa985ac73a8db02db7663381aa109510449d3819d37206caed27b33a65a8a6 \ + --hash=sha256:1aad64d99730d013cfc6debafed22783b4fc5a7f4b8bc744d2d8cf7dcc880551 \ + --hash=sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f \ + --hash=sha256:1f3ed25271581281f2fccb1adcedfcde4c07362eec69189b50baf6f90e3ae159 \ + --hash=sha256:1fb6f8fb9ff0b3a69f52c66ce397b0246583e9f28616231b0e32ca49259a5fa6 \ + --hash=sha256:214045a5bf00113a146ab9ee9730c44501af6723cdf1f6830932f7b5ef2e7af0 \ + --hash=sha256:26e15e4aecd8617dfbaecb37d223e365d7b39411fba20454be2670a96aa74cb5 \ + --hash=sha256:2c25dfa72943e4336ddb6b0ee4277b47a0c85bede0807530ec68103bf58e2c10 \ + --hash=sha256:2d8240cb4c16fd831074aa2b2cf9fc54664d826341d61c372245b96a74a49a9a \ + --hash=sha256:350ba9783ce969cf9f7ce6e6a9a58e1a6e2a19ca025b7ee448c4db727706212a \ + --hash=sha256:4c8a6d2ebce6305fd82fbefca827775437147052a976ee7c94b36a0c1b52ac6c \ + --hash=sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff \ + --hash=sha256:536f963710a4e63934d80ac0dc4f478804a83e9a84b6828018f25d09953ada33 \ + --hash=sha256:54a115e5a73b8fc44f0cebef486365a1894b5c9760685d4558b72b7c3eb846e0 \ + --hash=sha256:595d020938c84e320bcf40ad71089e108eac0d377cd018e14a8c094f39e98d85 \ + --hash=sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034 \ + --hash=sha256:6b05c171afb3aa07adbd20abc00aea86fe375beb0fdb9ef780ec5b7f63bab1c0 \ + --hash=sha256:6cef4bb1706dfec49243c05d921eefb4e190d41e2528b30d8035ea1f36b4c24a \ + --hash=sha256:6f24021b9f22bc6301c37b196974a92c1c18dccedb6fef3dd252e95f2d6adbe4 \ + --hash=sha256:71b39d9f935b6ec0f8753e3e2afb51e3efba6f2e05b68b32a40754d24bcd4a3c \ + --hash=sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec \ + --hash=sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58 \ + --hash=sha256:8617bbfae4486cf99c9f899966699428d19da931d06ca94ad3da986c76e15997 \ + --hash=sha256:86bff898a431c0fb71f7610b75726e75a54d47b37edc9d537f48de63bb3c0b90 \ + --hash=sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204 \ + --hash=sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e \ + --hash=sha256:93e1f5447e2b1e479d7bd74701e84746b86450cff1fc368b132d195e2b8f8211 \ + --hash=sha256:9a37475425b431b4d060f23b4f52cd2f3aef6bc7c654bd760adf0040eec9d435 \ + --hash=sha256:9deb49575e5b0b94ed72c8a64ec4d033381adc27e9060ae842971f697ba96104 \ + --hash=sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321 \ + --hash=sha256:a6391fafaba97500887132cd582abc6e19452b1ac775a47caa7b24490e152058 \ + --hash=sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0 \ + --hash=sha256:ac7bb1c52d445bd4f8f7f97fefe6abc3a084dc4d63df50d79b17fa2b78e89297 \ + --hash=sha256:adc1ada2662f8a5f960b8a10d9986897e7499ef07e06d4cfe7197f8cce923c07 \ + --hash=sha256:b00eefbcf0f292945c4b4dec2ae845389ef5bcdcd596e6e4328051db5b5ba694 \ + --hash=sha256:b0521d0f4aebb6e06189451025fa17a913287b13c03d5fe05c017333b654ea5b \ + --hash=sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034 \ + --hash=sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3 \ + --hash=sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231 \ + --hash=sha256:be5a8381859b6da607c84f4f7d6847725f1cf1853ef8a2c9e115b7d58bef47dc \ + --hash=sha256:befa1ae5bd6030b3f512b43ff3fa5290bbed6b84411a44244b14adf835f5b89d \ + --hash=sha256:bf63afbe037eb5d2fe87fbcc7778e61da53ebaf21d938a4515aa73b62532a5d4 \ + --hash=sha256:c00abe94c1a69d75d827dcf1c025b25c8a45d230b3bcd77a9020883a1b047653 \ + --hash=sha256:c2381f82999704f818e2c987a865050e285ec3621262c66d40f5a96c8f899f8e \ + --hash=sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b \ + --hash=sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9 \ + --hash=sha256:ccb32e0525d29e8b0572eb84c9a57af0e7a4e615726927506f55063c62414034 \ + --hash=sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958 \ + --hash=sha256:d1c89973648c85069c5046ad460f7b8a00218b29a2e42359ac8cc63e9ab94832 \ + --hash=sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563 \ + --hash=sha256:e01c918ac3d48e18a927cf7b14a26a3e29ff2bdf2eacb976da0aecd6a43ed034 \ + --hash=sha256:e6ab667ba76450084eb64013762c438ea76d9d29cc676dcd6c2e9892ba37f841 \ + --hash=sha256:e931e4f499e0dc7ef29d269a8e5b35dd722e5d14be07df6240166ea7c6532fae \ + --hash=sha256:f54660b0eb6b0b9f36e7fe1cdfdff472028dd0d14acd9b9b65098efbad059469 \ + --hash=sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c \ + --hash=sha256:f7fabeb6cea87d65f3b926de33d03fb016cfdc29314c90974383b5582ae72891 \ + --hash=sha256:f9579f383d1bf9df80081e72760e84960a7fd4f88cf0c9e535a8597c9bb646f5 \ + --hash=sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09 \ + --hash=sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e \ + --hash=sha256:ffdc76bfcae6b255dff75202c5e7feaf95b40246bc0a17944facc1fecf9f79ab + # via shapely +openpyxl==3.1.5 \ + --hash=sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2 \ + --hash=sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050 + # via -r requirements-dev.in +priority==2.0.0 \ + --hash=sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa \ + --hash=sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0 + # via hypercorn +quart==0.22.0 ; python_full_version < '3.13' \ + --hash=sha256:6ba567bb29e0ea66f7c0a0297c2b6225bb531e37dbf9b75dbf4a6e1713c4c934 \ + --hash=sha256:bb659545f1a8a287a14df9434b9225a3d4738362a3ed170744d0e03bb9447b50 + # via gerbonara +quart==0.23.1 ; python_full_version >= '3.13' \ + --hash=sha256:1ca848415910bd2eb75e9d9b452388f892a37be222602a373622e6c633d1efbf \ + --hash=sha256:78cf3a7249ab09f9e03d78b0b5e2472c4c09ce4615a99c2b1aa9a35261243b66 + # via gerbonara +rtree==1.4.1 \ + --hash=sha256:12de4578f1b3381a93a655846900be4e3d5f4cd5e306b8b00aa77c1121dc7e8c \ + --hash=sha256:3d46f55729b28138e897ffef32f7ce93ac335cb67f9120125ad3742a220800f0 \ + --hash=sha256:a7e48d805e12011c2cf739a29d6a60ae852fb1de9fc84220bbcef67e6e595d7d \ + --hash=sha256:b558edda52eca3e6d1ee629042192c65e6b7f2c150d6d6cd207ce82f85be3967 \ + --hash=sha256:c6b1b3550881e57ebe530cc6cffefc87cd9bf49c30b37b894065a9f810875e46 \ + --hash=sha256:d672184298527522d4914d8ae53bf76982b86ca420b0acde9298a7a87d81d4a4 \ + --hash=sha256:efa8c4496e31e9ad58ff6c7df89abceac7022d906cb64a3e18e4fceae6b77f65 \ + --hash=sha256:efe125f416fd27150197ab8521158662943a40f87acab8028a1aac4ad667a489 \ + --hash=sha256:f155bc8d6bac9dcd383481dee8c130947a4866db1d16cb6dff442329a038a0dc + # via gerbonara +shapely==2.1.2 \ + --hash=sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9 \ + --hash=sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b \ + --hash=sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3 \ + --hash=sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26 \ + --hash=sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d \ + --hash=sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7 \ + --hash=sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0 \ + --hash=sha256:1d0bfb4b8f661b3b4ec3565fa36c340bfb1cda82087199711f86a88647d26b2f \ + --hash=sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b \ + --hash=sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4 \ + --hash=sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c \ + --hash=sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf \ + --hash=sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40 \ + --hash=sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9 \ + --hash=sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6 \ + --hash=sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c \ + --hash=sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0 \ + --hash=sha256:40d784101f5d06a1fd30b55fc11ea58a61be23f930d934d86f19a180909908a4 \ + --hash=sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c \ + --hash=sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076 \ + --hash=sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a \ + --hash=sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566 \ + --hash=sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99 \ + --hash=sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2 \ + --hash=sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179 \ + --hash=sha256:7ae48c236c0324b4e139bea88a306a04ca630f49be66741b340729d380d8f52f \ + --hash=sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6 \ + --hash=sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a \ + --hash=sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801 \ + --hash=sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454 \ + --hash=sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618 \ + --hash=sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d \ + --hash=sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223 \ + --hash=sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350 \ + --hash=sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0 \ + --hash=sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c \ + --hash=sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af \ + --hash=sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8 \ + --hash=sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735 \ + --hash=sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1 \ + --hash=sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359 \ + --hash=sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc \ + --hash=sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf \ + --hash=sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715 \ + --hash=sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09 \ + --hash=sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc \ + --hash=sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd \ + --hash=sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26 \ + --hash=sha256:e38a190442aacc67ff9f75ce60aec04893041f16f97d242209106d502486a142 \ + --hash=sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc \ + --hash=sha256:eba6710407f1daa8e7602c347dfc94adc02205ec27ed956346190d66579eb9ea \ + --hash=sha256:ef4a456cc8b7b3d50ccec29642aa4aeda959e9da2fe9540a92754770d5f0cf1f \ + --hash=sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df \ + --hash=sha256:f6f6cd5819c50d9bcf921882784586aab34a4bd53e7553e175dece6db513a6f0 \ + --hash=sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94 \ + --hash=sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e \ + --hash=sha256:fe9627c39c59e553c90f5bc3128252cb85dc3b3be8189710666d2f8bc3a5503e + # via -r requirements-dev.in +werkzeug==3.1.8 \ + --hash=sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50 \ + --hash=sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44 + # via + # flask + # quart +wsproto==1.3.2 \ + --hash=sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584 \ + --hash=sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294 + # via hypercorn