diff --git a/PUBLIC_SOURCE_EXPORT.md b/PUBLIC_SOURCE_EXPORT.md index 2fed16f..ed24f59 100644 --- a/PUBLIC_SOURCE_EXPORT.md +++ b/PUBLIC_SOURCE_EXPORT.md @@ -1,3 +1,3 @@ # Public source export -This source snapshot was generated from the private canonical repository at revision `8aef76ab42f1ff10ee5a1b3ad94a184120687e47`. It contains no private Git history or operational evidence. Changes are published from the canonical repository. +This source snapshot was generated from the private canonical repository at revision `2aa63ba527a98b38b4d28f2a1bb85b1b1dbe7f6d`. It contains no private Git history or operational evidence. Changes are published from the canonical repository. diff --git a/PUBLIC_SOURCE_MANIFEST.json b/PUBLIC_SOURCE_MANIFEST.json index 36dd3e2..624ed7e 100644 --- a/PUBLIC_SOURCE_MANIFEST.json +++ b/PUBLIC_SOURCE_MANIFEST.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceRevision": "8aef76ab42f1ff10ee5a1b3ad94a184120687e47", + "sourceRevision": "2aa63ba527a98b38b4d28f2a1bb85b1b1dbe7f6d", "files": [ { "path": ".dockerignore", @@ -2037,6 +2037,21 @@ "sha256": "90d0a0017a33ac6e25b74b42f7209d54fcb1424854a56a4d22332fc8af65044c", "bytes": 702 }, + { + "path": "infrastructure/images/contextforge/candidate/build-tools.lock", + "sha256": "4ba5f91f6473fc4fc9971b2353629203099d9409f537895972e60a65890af02f", + "bytes": 741 + }, + { + "path": "infrastructure/images/contextforge/candidate/locks.json", + "sha256": "28029214650a4d864ff1f6cfdecf4f2c2e1f8438e3f792b35ce52fda90d2d896", + "bytes": 3132 + }, + { + "path": "infrastructure/images/contextforge/candidate/production-requirements.lock", + "sha256": "daaa815ef1ce2e58c60d9bac840f5754a12eab4b18642713e197d3f5f9467c30", + "bytes": 131433 + }, { "path": "infrastructure/images/contextforge/compat-image.env", "sha256": "ff35e0be3f6abfe440fb901fec92219410d3d0aef78bc75b7cb0fb679219a81f", @@ -2047,15 +2062,30 @@ "sha256": "d6f502ecb2f98d929c7c050a67313e3802271ab123cfedd6b3ea7c886a558560", "bytes": 3095 }, + { + "path": "infrastructure/images/contextforge/os-bases/Dockerfile.builder", + "sha256": "0c7962ca473f7b61073e4e57b0211fc84cdedc1a89effcda8d9e4ee796a0a0e0", + "bytes": 1466 + }, + { + "path": "infrastructure/images/contextforge/os-bases/Dockerfile.runtime", + "sha256": "5ff86c30ed089fc0d74f1b05190059990bf1b1870b66fa33f3f6d5a3b2414848", + "bytes": 1618 + }, { "path": "infrastructure/images/contextforge/runtime-contract.json", "sha256": "11345ea89e229e7bf164eb037b525b685a5dfb143c08a220544c0b6cdde411d9", "bytes": 700 }, + { + "path": "infrastructure/images/contextforge/security-requirements.txt", + "sha256": "1fdf9cd965f70ce32364bfdb538a5d93cc24559b4a4b69fcd35fb1190d994ecb", + "bytes": 187 + }, { "path": "infrastructure/images/docker-read-broker/Dockerfile", - "sha256": "453d0024e175c77105544756c98f890a7839be2c8475ecbadbff5dddd0f91300", - "bytes": 1153 + "sha256": "17d49ab4af92d0c26a0a62491cc5c296ad57d9f758f031d0d57e343361a079cd", + "bytes": 1391 }, { "path": "infrastructure/images/docker-read-broker/broker_health.py", @@ -2069,8 +2099,8 @@ }, { "path": "infrastructure/images/observability/Dockerfile", - "sha256": "27082e021d0f808cb83c8a9f8033b1bebedbbf8ceec9d2407e57b079d0b98088", - "bytes": 1012 + "sha256": "c9804d64b0e137721ec5c57e1a7956483a38109dc3063262b19d94a45991e6f9", + "bytes": 1250 }, { "path": "infrastructure/images/observability/collector.py", @@ -2084,8 +2114,8 @@ }, { "path": "infrastructure/images/python-service/Dockerfile", - "sha256": "f00cf3e66c3918a362b02a1dce1efc4ebe383db27fc06474b697e1b220098f72", - "bytes": 2219 + "sha256": "011a5b3d2900b996ab3f522d9de09e79cfc1cae76c91aa6c03994c660f0965e3", + "bytes": 2457 }, { "path": "infrastructure/images/python-service/health_server.py", @@ -2389,8 +2419,8 @@ }, { "path": "scripts/build_contextforge_compat_image.sh", - "sha256": "df0fa058b3837add913de32da29bea7a464bcf7cfadff730b3b61edfb431f4da", - "bytes": 8234 + "sha256": "b77dee65d61ff2425a1f4de75e38d854c738ae21713ecd992c5609dde866618e", + "bytes": 17296 }, { "path": "scripts/check_boundaries.py", @@ -2404,14 +2434,19 @@ }, { "path": "scripts/check_cve_exceptions.py", - "sha256": "3a140095f2cb6ec7c23760503410236dd644d780c7456d3248276e1ce9bf507d", - "bytes": 6761 + "sha256": "6e72a325f0c4733db0be84c2afb90e57dd15060101f2b0d6d3509d6a5031e936", + "bytes": 14228 }, { "path": "scripts/contextforge_bootstrap.py", "sha256": "e7ae621d1803f98ce7022c673435088a24863761448b162f8479d403eb63b225", "bytes": 12239 }, + { + "path": "scripts/contextforge_candidate_recipe.py", + "sha256": "d33f5c11d10ab2987128adc264a20ef4dc9913b80c9e55563a1908b2c7154962", + "bytes": 21090 + }, { "path": "scripts/contextforge_runtime_contract.py", "sha256": "3a93aa6b278a974e7b09688f34ecb9016f298cc859c724e208ff418e5336d6a3", @@ -2424,8 +2459,8 @@ }, { "path": "scripts/draft_cve_exceptions.py", - "sha256": "aaebac11f274002eb86562f3ca62a4601bd6eee78b8cb987934db3c407cba000", - "bytes": 4176 + "sha256": "15bc47c90ed7f83d790219555d31939e88e92a0cffe02eb0b2da04035eff08b3", + "bytes": 6798 }, { "path": "scripts/export-public-mirror.py", @@ -3029,8 +3064,8 @@ }, { "path": "tests/fixtures/wp200-compose-config.env", - "sha256": "beb0865fd116bdf1fae350803735ec15f273f326b0806b5c9bbf330807673aa6", - "bytes": 2208 + "sha256": "4c341dd335e4e32d9f25ca5cc142ad4013d6c1e7c4012dd755840b1e5458441c", + "bytes": 2326 }, { "path": "tests/test_appops_connector.py", @@ -3074,8 +3109,8 @@ }, { "path": "tests/test_compose.py", - "sha256": "2d02f6106ae5bf7cd28e7e5202be7ed721dd9f527f2228ba7df5dcb4a7d9be87", - "bytes": 16814 + "sha256": "b0cf2be5b0b53aa6698a30568ad7999e113daa37bd831aa535d1866a0d373fc6", + "bytes": 18737 }, { "path": "tests/test_contextforge.py", @@ -3087,10 +3122,15 @@ "sha256": "c2aba6c99e221b31ea0f562b150e60894051d5aa98ae8d51d8ac76ad7147a533", "bytes": 15850 }, + { + "path": "tests/test_contextforge_candidate_recipe.py", + "sha256": "379a240d4ca41dcd04e514fa721c692cd5b6cf23d0d5945f99039b24e2ef3474", + "bytes": 17265 + }, { "path": "tests/test_contextforge_compat_image_contract.py", - "sha256": "430332785d64ddab20e42727c00aeae3a54ffcdb9f80a56dfe5441e09791844a", - "bytes": 4480 + "sha256": "d415e2bc7b64144fb9c3f5b0115ab2e8b828516053bc0bbdce3b46e710f87b56", + "bytes": 24559 }, { "path": "tests/test_contextforge_integration.py", @@ -3099,8 +3139,8 @@ }, { "path": "tests/test_contextforge_production_image_contract.py", - "sha256": "fc6fe95a81e2e2265c82f2b3ed679e9d7791735070c9bfcf0926005215307462", - "bytes": 1787 + "sha256": "45a656050f73b6e64a626c2407f5ba6fffcfe25886bb42cd408c57a8d64e2633", + "bytes": 2257 }, { "path": "tests/test_contextforge_promotion_preflight_contract.py", @@ -3244,8 +3284,8 @@ }, { "path": "tests/test_ingestion_catalog_compose.py", - "sha256": "835bc9218a27aa26ace1882daf0f5142a54e135ead7575c0f88d89d0272dde24", - "bytes": 10746 + "sha256": "60aa6957947ba96974ed0a6941c5e40b8871ec612919f0812e1f4bc1b427a0da", + "bytes": 10888 }, { "path": "tests/test_ingestion_connector.py", @@ -3409,8 +3449,8 @@ }, { "path": "tests/test_wp220_cve_exceptions.py", - "sha256": "afdd0aebfca0c2bb379edbd91d700f27fb6684f85a3b77ed339e7f7f901b03b4", - "bytes": 6217 + "sha256": "ceae7f0a167ae8083be4daee0e291b2cb669c90877e2a88113861e85d038b07b", + "bytes": 18080 }, { "path": "tests/test_wp220_export.py", diff --git a/infrastructure/images/contextforge/candidate/build-tools.lock b/infrastructure/images/contextforge/candidate/build-tools.lock new file mode 100644 index 0000000..ff36b76 --- /dev/null +++ b/infrastructure/images/contextforge/candidate/build-tools.lock @@ -0,0 +1,12 @@ +packaging==26.2 \ + --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \ + --hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661 +pip==26.2.1 \ + --hash=sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e \ + --hash=sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f +setuptools==82.0.1 \ + --hash=sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9 \ + --hash=sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb +wheel==0.46.2 \ + --hash=sha256:33ae60725d69eaa249bc1982e739943c23b34b58d51f1cb6253453773aca6e65 \ + --hash=sha256:3d79e48fde9847618a5a181f3cc35764c349c752e2fe911e65fa17faab9809b0 diff --git a/infrastructure/images/contextforge/candidate/locks.json b/infrastructure/images/contextforge/candidate/locks.json new file mode 100644 index 0000000..aaab790 --- /dev/null +++ b/infrastructure/images/contextforge/candidate/locks.json @@ -0,0 +1,65 @@ +{ + "baseImages": [ + { + "arg": "UBI_BASE", + "childManifestDigest": "sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483", + "imageId": "sha256:c6bb70a7a340ed1047e7cb934aa55b086ee68a5eb9ba6b2ad2e6825c7184a3e8", + "parentReference": "registry.access.redhat.com/ubi10@sha256:1e32d829a4de713a835d702d3bc05f0c448a08590796281b7dec181912e9aeb8", + "platform": "linux/amd64", + "reference": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder@sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483", + "rpmInventorySha256": "6a8c0d4f66fc5a38008541ad15614711e7f536cd18c70c02d2eb0691552b88b0", + "sourceTag": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder:sha256-c6bb70a7a340ed1047e7cb934aa55b086ee68a5eb9ba6b2ad2e6825c7184a3e8" + }, + { + "arg": "NODEJS_IMAGE", + "childManifestDigest": "sha256:0f110e68682f4a49188ba4db786eb4f207b119420913fc76bb5ba9512ffbef06", + "platform": "linux/amd64", + "reference": "registry.access.redhat.com/ubi10/nodejs-24@sha256:0f110e68682f4a49188ba4db786eb4f207b119420913fc76bb5ba9512ffbef06", + "sourceTag": "registry.access.redhat.com/ubi10/nodejs-24:1784092609" + }, + { + "arg": "UBI_MINIMAL", + "childManifestDigest": "sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236", + "imageId": "sha256:fd94e2643dfc0401790d0528d3740bb2f0c04709e743f6c774a4e8a38606e40b", + "parentReference": "registry.access.redhat.com/ubi10/ubi-minimal@sha256:afbfb5c18c628796fed3ad3122dcf096e0b2dec9dad3fbabe8620de06bc6f078", + "platform": "linux/amd64", + "reference": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236", + "rpmInventorySha256": "a74d60ecb43d1aa561874305d1a9c62ac35c99a14c1dc275ba1656f14df7d034", + "sourceTag": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime:sha256-fd94e2643dfc0401790d0528d3740bb2f0c04709e743f6c774a4e8a38606e40b" + } + ], + "buildTools": [ + "pip==26.2.1", + "setuptools==82.0.1", + "wheel==0.46.2" + ], + "constraints": [ + "mcp==1.30.0", + "PyJWT==2.15.0", + "urllib3==2.8.0" + ], + "extras": [ + "redis", + "postgres", + "observability", + "granian", + "plugins", + "llmchat" + ], + "freshnessOverrideUtc": "2026-10-02T00:00:00Z", + "globalExcludeNewerUtc": "2026-09-22T00:00:00Z", + "platform": "linux/amd64", + "python": "3.12", + "requirements": { + "build-tools.lock": "4ba5f91f6473fc4fc9971b2353629203099d9409f537895972e60a65890af02f", + "production-requirements.lock": "daaa815ef1ce2e58c60d9bac840f5754a12eab4b18642713e197d3f5f9467c30" + }, + "schema": "itworx.contextforge-candidate-locks/v1", + "upstreamCommit": "1e58a0434a78bbdbe1e975abeb0bed1043ff14c1", + "upstreamFiles": { + "Containerfile": "2ab0187f3a63eced093a1dec3141ec3e30ef879eb7de0dfd9e4bc5fce33f64f2", + "pyproject.toml": "cd365566f922546e569c65475d78cd53e5d49f10df2c82534efa607ffcf55e4b", + "uv.lock": "c5297a4701e4a0ecb9903bd3f4aa1c8cf40cf4e5568880fcb8f4078703589df1" + }, + "uvVersion": "0.12.10" +} diff --git a/infrastructure/images/contextforge/candidate/production-requirements.lock b/infrastructure/images/contextforge/candidate/production-requirements.lock new file mode 100644 index 0000000..952030c --- /dev/null +++ b/infrastructure/images/contextforge/candidate/production-requirements.lock @@ -0,0 +1,1647 @@ +alembic==1.18.5 \ + --hash=sha256:06d8ba9d04558022f5395e9317de03d270f3dced49cee01f89fe7a13c26f14bc \ + --hash=sha256:1554982221dd17e9a749b53902407578eb305e453f71999e8c7f0a48389fff8e +annotated-doc==0.0.4 \ + --hash=sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320 \ + --hash=sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4 +annotated-types==0.7.0 \ + --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ + --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 +ansicon==1.89.0 ; sys_platform == 'win32' \ + --hash=sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1 \ + --hash=sha256:f1def52d17f65c2c9682cf8370c03f541f410c1752d6a14029f97318e4b9dfec +anyio==4.14.1 \ + --hash=sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72 \ + --hash=sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e +argon2-cffi==25.1.0 \ + --hash=sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1 \ + --hash=sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741 +argon2-cffi-bindings==25.1.0 \ + --hash=sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99 \ + --hash=sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6 \ + --hash=sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44 \ + --hash=sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2 \ + --hash=sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0 \ + --hash=sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98 \ + --hash=sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500 \ + --hash=sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94 \ + --hash=sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d \ + --hash=sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d \ + --hash=sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a +async-timeout==5.0.1 ; python_full_version < '3.11.3' \ + --hash=sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c \ + --hash=sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3 +attrs==26.1.0 \ + --hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 \ + --hash=sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32 +base58==2.1.1 \ + --hash=sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2 \ + --hash=sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c +blessed==1.45.0 \ + --hash=sha256:afcc23a07e965534dd4669951d2397397dbece46e18998eee9d24d40ac503765 \ + --hash=sha256:d12abbbcfb5fdd80bac33ea7e22f1a163ff462f5c05f312159e04c5fee460c72 +brotli==1.2.0 \ + --hash=sha256:022426c9e99fd65d9475dce5c195526f04bb8be8907607e27e747893f6ee3e24 \ + --hash=sha256:072e7624b1fc4d601036ab3f4f27942ef772887e876beff0301d261210bca97f \ + --hash=sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c \ + --hash=sha256:15b33fe93cedc4caaff8a0bd1eb7e3dab1c61bb22a0bf5bdfdfd97cd7da79744 \ + --hash=sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a \ + --hash=sha256:260d3692396e1895c5034f204f0db022c056f9e2ac841593a4cf9426e2a3faca \ + --hash=sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6 \ + --hash=sha256:2a7f1d03727130fc875448b65b127a9ec5d06d19d0148e7554384229706f9d1b \ + --hash=sha256:2e1ad3fda65ae0d93fec742a128d72e145c9c7a99ee2fcd667785d99eb25a7fe \ + --hash=sha256:350c8348f0e76fff0a0fd6c26755d2653863279d086d3aa2c290a6a7251135dd \ + --hash=sha256:35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84 \ + --hash=sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18 \ + --hash=sha256:40d918bce2b427a0c4ba189df7a006ac0c7277c180aee4617d99e9ccaaf59e6a \ + --hash=sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48 \ + --hash=sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5 \ + --hash=sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c \ + --hash=sha256:7a47ce5c2288702e09dc22a44d0ee6152f2c7eda97b3c8482d826a1f3cfc7da7 \ + --hash=sha256:7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b \ + --hash=sha256:844a8ceb8483fefafc412f85c14f2aae2fb69567bf2a0de53cdb88b73e7c43ae \ + --hash=sha256:898be2be399c221d2671d29eed26b6b2713a02c2119168ed914e7d00ceadb56f \ + --hash=sha256:9c79f57faa25d97900bfb119480806d783fba83cd09ee0b33c17623935b05fa3 \ + --hash=sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab \ + --hash=sha256:aa47441fa3026543513139cb8926a92a8e305ee9c71a6209ef7a97d91640ea03 \ + --hash=sha256:acec55bb7c90f1dfc476126f9711a8e81c9af7fb617409a9ee2953115343f08d \ + --hash=sha256:adedc4a67e15327dfdd04884873c6d5a01d3e3b6f61406f99b1ed4865a2f6d28 \ + --hash=sha256:af43b8711a8264bb4e7d6d9a6d004c3a2019c04c01127a868709ec29962b6036 \ + --hash=sha256:b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44 \ + --hash=sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8 \ + --hash=sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f \ + --hash=sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a \ + --hash=sha256:e99befa0b48f3cd293dafeacdd0d191804d105d279e0b387a32054c1180f3161 +brotlicffi==1.2.0.1 ; platform_python_implementation != 'CPython' \ + --hash=sha256:37cb587d32bf7168e2218c455e22e409ad1f3157c6c71945879a311f3e6b6abf \ + --hash=sha256:6f3314a3476f59e5443f9f72a6dff16edc0c3463c9b318feaef04ae3e4683f5a \ + --hash=sha256:82ea52e2b5d3145b6c406ebd3efb0d55db718b7ad996bd70c62cec0439de1187 \ + --hash=sha256:91ba5f0ccc040f6ff8f7efaf839f797723d03ed46acb8ae9408f99ffd2572cf4 \ + --hash=sha256:9d6ba65dd528892b4d9960beba2ae011a753620bcfc66cf6fa3cee18d7b0baa4 \ + --hash=sha256:be9a670c6811af30a4bd42d7116dc5895d3b41beaa8ed8a89050447a0181f5ce \ + --hash=sha256:c20d5c596278307ad06414a6d95a892377ea274a5c6b790c2548c009385d621c \ + --hash=sha256:da2e82a08e7778b8bc539d27ca03cdd684113e81394bfaaad8d0dfc6a17ddede \ + --hash=sha256:e015af99584c6db1490a69a210c765953e473e63adc2d891ac3062a737c9e851 \ + --hash=sha256:f2a5575653b0672638ba039b82fda56854934d7a6a24d4b8b5033f73ab43cbc1 +certifi==2026.6.17 \ + --hash=sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432 \ + --hash=sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db +cffi==2.0.0 \ + --hash=sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb \ + --hash=sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b \ + --hash=sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75 \ + --hash=sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e \ + --hash=sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe \ + --hash=sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91 \ + --hash=sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187 \ + --hash=sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c \ + --hash=sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94 \ + --hash=sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba \ + --hash=sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529 \ + --hash=sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca \ + --hash=sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6 \ + --hash=sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743 \ + --hash=sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5 \ + --hash=sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5 \ + --hash=sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d \ + --hash=sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b \ + --hash=sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93 \ + --hash=sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27 \ + --hash=sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037 \ + --hash=sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26 \ + --hash=sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c \ + --hash=sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414 \ + --hash=sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664 \ + --hash=sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9 \ + --hash=sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062 \ + --hash=sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe \ + --hash=sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92 \ + --hash=sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5 \ + --hash=sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d \ + --hash=sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26 \ + --hash=sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b \ + --hash=sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c \ + --hash=sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5 \ + --hash=sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18 \ + --hash=sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3 \ + --hash=sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2 +charset-normalizer==3.4.7 \ + --hash=sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4 \ + --hash=sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c \ + --hash=sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5 \ + --hash=sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b \ + --hash=sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c \ + --hash=sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7 \ + --hash=sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb \ + --hash=sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c \ + --hash=sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1 \ + --hash=sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df \ + --hash=sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e \ + --hash=sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38 \ + --hash=sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110 \ + --hash=sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18 \ + --hash=sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d \ + --hash=sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48 \ + --hash=sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e \ + --hash=sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5 \ + --hash=sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d \ + --hash=sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c \ + --hash=sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116 \ + --hash=sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d \ + --hash=sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6 \ + --hash=sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2 \ + --hash=sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a \ + --hash=sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265 \ + --hash=sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15 \ + --hash=sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7 \ + --hash=sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8 \ + --hash=sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b \ + --hash=sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00 \ + --hash=sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66 \ + --hash=sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad \ + --hash=sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d \ + --hash=sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5 \ + --hash=sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7 \ + --hash=sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49 \ + --hash=sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1 \ + --hash=sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c \ + --hash=sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd \ + --hash=sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e \ + --hash=sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b \ + --hash=sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859 \ + --hash=sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46 \ + --hash=sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a \ + --hash=sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215 \ + --hash=sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063 \ + --hash=sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832 \ + --hash=sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6 \ + --hash=sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464 +click==8.4.2 \ + --hash=sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6 \ + --hash=sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76 +colorama==0.4.6 ; sys_platform == 'win32' \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 +cpex==0.1.1 \ + --hash=sha256:4b0c753979a8c52b0e5eea2269e67328ea96d3bef477da3f3ec200ff297bcddc \ + --hash=sha256:64ddfda6239f975bc5c0fa841e036e660ac478eb42f03225f7d3fe205680e049 +cpex-encoded-exfil-detection==0.3.5 \ + --hash=sha256:12dc905f4ec0209d21cfe17a8764b9dacd237d7d5374c3e4868d62bef5c5303a \ + --hash=sha256:8ca18be43b7fa004508bf4285e129847347d012c6b62dc77699edfeaa0504b0d \ + --hash=sha256:a4a2d35de6396e4bfbde39b5fff792420d01a33cbd1c2dd7a6f3268fd5632deb \ + --hash=sha256:aba6934e10b39471b6e652a4a614d870d5c4595cb91759a29f0361642581e228 \ + --hash=sha256:d4e2c8044f8ca797386b66b5628db27a7be0645badc668ded89717bfdcd7d7e7 \ + --hash=sha256:ec8585b771dda02b373ecc124c2205357b3bfeef4750337a9ccdf4e715ce760a \ + --hash=sha256:f436753df02fce48c0377c50a2d3a18f1d6377d58439fce557c2f0815aebfcf5 +cpex-pii-filter==0.3.6 \ + --hash=sha256:0c3d11f1eb306ec96cf6e6daf1bec9c0973d43527153ab60e76c8ee4ee8cdeb8 \ + --hash=sha256:4fb8d6d8a6935333f560f2af8eb53381d327237d3a2166b4dbdf0cccba7b0c68 \ + --hash=sha256:720c7a27f2b5b8c049a4142c5a18cc3e272d1bf12cea90b56a79b363257ca460 \ + --hash=sha256:b1e75e56174d405ba3e052097e1f5720a589d5f1a654a15a66b86b6b27129052 \ + --hash=sha256:c1e2c7702d9b79f7e5cfd1ac63f4e4eb15a438a2f2b7a54818e228ce2e4a4ebe \ + --hash=sha256:cbb23b7801aafb5686119f4306a5b33820bdaa30104d42c89e1bec713c177ca1 \ + --hash=sha256:e3c602ce2bafaa01888d78b5dbb1bd78f78d280d3d7104292551510221fdf881 +cpex-rate-limiter==0.1.6 \ + --hash=sha256:067c6e7856098e49d5c1fd309ce93a1ef943cdee6ff35426cb6cbeba987e833e \ + --hash=sha256:4b74f5839fc2ad26fb2a01f62dffc35310103ec6a8b18dd4a8aa6f5180b081d5 \ + --hash=sha256:7884cc88b5e96f2a687516bcb1673c36df7fd5cb4a1a0a19e14a4b9f1427fe40 \ + --hash=sha256:9f0c5859439ad601231c772f491a424f8d854feef3aad15888d8d663d0f48a7f \ + --hash=sha256:be142d7bad309cca57e192e67537e123d2957dacfe44a915502a6e42a0d7703f \ + --hash=sha256:f0886e9693f14e10d1fd7ac0df6174a2eebb87749083570999160af46eeba549 \ + --hash=sha256:f11b4cc95fa8dfb69449ee9c740d3c2eab80efe437b2d871ec845b59b33d77e2 +cpex-retry-with-backoff==0.3.1 \ + --hash=sha256:2fc0166929b7a8658ef256559119a2af3612a8d36d522bd822310a0f3c8cf4ee \ + --hash=sha256:377f08e2c076ba462154518f4845d723c10d601737618b0328859b42f1a51cd1 \ + --hash=sha256:405a5e9868eaadbb86938f582d6847c32bb441dff3980753f87a1987869f0416 \ + --hash=sha256:41b008b966de5f5bd3cb2eac40293eba4f6e937e67bd029af731734a7df9da9d \ + --hash=sha256:670fc43949cc3187e72f8d22e9ef2b7089e81455cdd28c6fe274a57fb4f93610 \ + --hash=sha256:71b573b25f1d4b3bf2fa29198cef634acfeb52ac147f78c647e23430226bd754 \ + --hash=sha256:b2ecce98d944c766084a6153b7ec51298f60336254b2224bd2664596cc47364c +cpex-secrets-detection==0.3.6 \ + --hash=sha256:11e751b9c0f4dcc4b4f8536ed2860d0ab7c4651b1e28847faa09ec1b7ea9221a \ + --hash=sha256:2f8389f697e3306fb2975335e658c115b2e05f162f5921c584ce773328399c63 \ + --hash=sha256:5410563d2f5eccdf317d5d3595e0cdf9e2d9615eacdfa969f911f2c0a63df0fd \ + --hash=sha256:79a7101b9d2d1f609ce37f7b7a33bf45f86016be2866037005eeaeeca5825aee \ + --hash=sha256:94e780bb544ee34430735d31854c221783d8173b56f1174f5655415c5fdc898b \ + --hash=sha256:bfd857242f78a4f06a785d6e92cf26907771b27096a41436fc2aa5c6f19d9ceb \ + --hash=sha256:c6a77f2105d27146b1808eabbf73c5be71620c44206b47583b5af021003c66b3 +cpex-url-reputation==0.3.4 \ + --hash=sha256:1501ec97bf746dbb6fba9127c4b73a0b9ef7c39ac8131475348e98bc8d88e46b \ + --hash=sha256:58fe361c4b925c2b7373c7c97b40ed9879c873b09318cad202961328feeb53aa \ + --hash=sha256:6f17f62d285309fc0bb17a8cfba462534a2a19b9968a8929d13461527d8c7587 \ + --hash=sha256:7504ac0916e6fea7f3221a78079a479bfb28e3eb3eb842d3f26c4ee330d0310e \ + --hash=sha256:b91db67405dc00e93d147cc4fb6688ac8983c6935d8b41709db33f3c0c249313 \ + --hash=sha256:d059fbd921ddd20f941a06e18ddad8aa49bc8a0b79d19f340e65706847ea084f \ + --hash=sha256:e97bf605d931d8a478236cbc34e6170a07189fd3670fda9bb2cc3c03ae054ea7 +cryptography==49.0.0 \ + --hash=sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001 \ + --hash=sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6 \ + --hash=sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c \ + --hash=sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325 \ + --hash=sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69 \ + --hash=sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36 \ + --hash=sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc \ + --hash=sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6 \ + --hash=sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b \ + --hash=sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18 \ + --hash=sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db \ + --hash=sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b \ + --hash=sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2 \ + --hash=sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459 \ + --hash=sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e \ + --hash=sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21 \ + --hash=sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8 \ + --hash=sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9 \ + --hash=sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db \ + --hash=sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64 \ + --hash=sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5 \ + --hash=sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615 \ + --hash=sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f \ + --hash=sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6 \ + --hash=sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838 \ + --hash=sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9 \ + --hash=sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7 \ + --hash=sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68 \ + --hash=sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e \ + --hash=sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a \ + --hash=sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4 \ + --hash=sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493 \ + --hash=sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b +distro==1.9.0 \ + --hash=sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed \ + --hash=sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2 +dnspython==2.8.0 \ + --hash=sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af \ + --hash=sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f +editor==1.8.0 \ + --hash=sha256:7d47ff88ae6c5f6c43d28c30b6f7fd59a24741175a1771ab06c969d946d7dfd0 \ + --hash=sha256:b07e1bbcb8b33f05c2e6ed3ce77ee9756354ada840a18aad7c0536d967fe4c0b +email-validator==2.3.0 \ + --hash=sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4 \ + --hash=sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426 +fastapi==0.139.0 \ + --hash=sha256:99ab7b2d92223c76d6cf10757ab3f89d45b38267fc20b2a136cf02f6beac3145 \ + --hash=sha256:cf15e1e9e667ddb0ad63811e60bd11390d1aac838ca4a7a23f421807b2308189 +filelock==3.29.5 \ + --hash=sha256:6e6034c57a00a020e767f2614a5539863f056de7e7991d6d1473aef7ff73f156 \ + --hash=sha256:8af830889ba3a0ffcefbd6c7d2af8a54012058103771f2e10848222f476a1693 +googleapis-common-protos==1.75.0 \ + --hash=sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd \ + --hash=sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed +granian==2.7.9 \ + --hash=sha256:00530458f3cbfcd922cd8b8c2eb2cc17123ff84850de946a4c4839e35e9b74fd \ + --hash=sha256:02bed3191731ff6990308a322fb810890d211f7614d4c67825e92d93be7d2d97 \ + --hash=sha256:03af38185c3fd321713123fafbeb711ca84a7b221c8d159daa82d7c57cb02141 \ + --hash=sha256:096d9a3396b13826bc63d2cf424ed04daf1ea077beed361d48dca2d55cb4b527 \ + --hash=sha256:12da3d84a9dce7706a19f6a98e8f7c3624721ff66bc34a60cfc4d58e2d6593b9 \ + --hash=sha256:18e3f870a8c42500fe6c8d73d24a6888920e2a8740810ef1ddb83b0b67ce643e \ + --hash=sha256:22caac0f82396d792860f7f0ab6012231b1578ba7e50427306c4f17d1e146dfb \ + --hash=sha256:233baf237d4d3768b4ca6cb6d26546de242e437e1af6050405dcc4673032977f \ + --hash=sha256:23aa08e4d7563f0acb45424676a7d892a146493b5db0895efb5bc8e5121e3051 \ + --hash=sha256:2836fd595a144be7e70faebddf1ce7b4e0c136a7017d75660b88c8da63c0f0ff \ + --hash=sha256:331c1050b232ae0366c6e8d26aa4ceb5ad4247216b9c613854c27a8bd14c9205 \ + --hash=sha256:332a6111fff536dca96776f33bfec9415bf5b66c53f46604b9f2d2b5b7a6036a \ + --hash=sha256:3d741def2ad09c8e30880c909023c6d6b78ebe528c5c2e95de8523d967f625f1 \ + --hash=sha256:44564f8d0f2971550285f21ff815e75b0c5761a0230d5ef3f05d8af851628ee6 \ + --hash=sha256:4918fc299a2b429a92fcee7006c0df63856bcc5e9dfc969be16a04ee1a7b1a81 \ + --hash=sha256:497c99fc3d4fe0342add24e3c53da78f0d9505ec332dae9075527e00918617d4 \ + --hash=sha256:5dda81fbf58d8f173b9990e6300525359dc2641b88f03bbbe31cc693db5f530b \ + --hash=sha256:61270c22d6172fbb6f8e363bad19e8dcaefde27a9b0685bbffaa09835fdf44e3 \ + --hash=sha256:64eb08bcf4aca0375fae95a27ca2e78e9dfef7ccae8e12daaac8b2f0bd1cc718 \ + --hash=sha256:6d08149dfa9777ba281ddf7d7df7ae5473cf6c2fa8815ac33301a24acf33e875 \ + --hash=sha256:73e8fe3d162c66d1eddaee825e59379bbf7a384ff776ae984baf796cf1b81fe3 \ + --hash=sha256:7a720940970ce47b4b348d4e3c9fefe6ce0a40c46ac851c2e4cc0dcf5bd6e2a7 \ + --hash=sha256:7edc7c798d01a8afb0ab8925d009a9403ab9585d11d450b8e7c4559d963101f5 \ + --hash=sha256:809ec8a9f44c69c49b158811ad78a3195d5e361662ce93dcb7afb848f5f2fd10 \ + --hash=sha256:887f0f1e045314044712a2e3799991c7be0ba4129f6806b82e40f502417694d9 \ + --hash=sha256:8adc5757b1f1d3e5ad0f50787f459ea33fb8c4434d3d4c3ad2a0694a761c2ce5 \ + --hash=sha256:8faaa4e51758cf6d2b3b5b228d1cb9cc4d1be460d59ebb71951435505a2ee48a \ + --hash=sha256:92f9460c68dfc41d57d255baf6dc3b15ade38b1c19fcb40ce1dd3aa74058b530 \ + --hash=sha256:9aa2087ae2103e99ba169f53d718258205bf2ace7df87c9b6dd52e3f71a90335 \ + --hash=sha256:a9395c947ef1884567e63b5c49bd53f4868daf94ac7047d85e73f94efc53c9f4 \ + --hash=sha256:ac516f3cbdce733578ee91a8139d624a1dccf9ab782fb9f3ab58254ebe6c29ad \ + --hash=sha256:b13dd27d32b4fb2ff8a7fd79f80de78bd706c946a8cb1c6ea8a7b83a56086c5e \ + --hash=sha256:b3000658f76ef2069aee455d3a0835e36fb073e8fd6e95d777cc0179eb97f5b2 \ + --hash=sha256:b4dbf1e14f2b381a0a9d043598b8d496956d7e0e89ab43e4c04f415485be85a2 \ + --hash=sha256:b87dd3e65a4ce09ae2113b3fa36f741aaa2aa6e4c31f16bf67c64b2b7c3ade02 \ + --hash=sha256:be49dc58a4220f3af127d26d2ffff58f75afabf7b790681e213bfcb95c51424b \ + --hash=sha256:cae7ccfd34519241e92311e966763650f839b07b1195541cb0f5c180ef22df99 \ + --hash=sha256:cde124d00b8702300779badf9edb757449144a28fdedf6e8ab4d169d54f74fa4 \ + --hash=sha256:d019f88e7944c3e3967da833e758c85cb6c35c7a82a8185eba23243b5cf24b7c \ + --hash=sha256:d4ddd82390b6fb9c025007f7ccd92e99d244211898a7851103f28688889dd905 \ + --hash=sha256:d52c262b5e0d345c0c0684b7536ea42f370a7bbb4720b1b625d60d84d87c81be \ + --hash=sha256:e04d86c24948dbf3de0b6b1ac9a9d6c82514f5a6c4f27fe49991e5178a1d0d9f \ + --hash=sha256:e155eb6d3c8827ef3821c80ae6a5f7a61f37562d121240128ce099e3db06ad2f \ + --hash=sha256:e49f859672fe1e8e062f3be791bec2b3169505719ccf83459ff48aac52ee1d0f \ + --hash=sha256:e79316015dd68624e021281b3d0e2d9446f04160db4f14140561fe4c0ffeaaa0 \ + --hash=sha256:ea963e616edba406969579e296f853bee164db23c1a63b8d2268459797c64810 \ + --hash=sha256:ed501eafd6cb3c63924466860bf478ad23691d7ad16678b2069e75b5328a074c \ + --hash=sha256:eecd2aa017aa92bc165a9ad33c494e1cfcbfc68a5f055b43e729749899590867 \ + --hash=sha256:ff89b203cebba0780efa0360b6e23520323d05f799a610d4a07a5681067b7249 +greenlet==3.5.3 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' \ + --hash=sha256:0f41e4a05a3c0cb31b17023eff28dd111e1d16bf7d7d00406cd7df23f31398a7 \ + --hash=sha256:0f71be4920368fe1fabeeaa53d1e3548337e2b223d9565f8ad5e392a75ba23fc \ + --hash=sha256:1c514a468149bf8fbbab874188a3535cd8a48a3e353eb53a3d424296f8dbacd3 \ + --hash=sha256:215275b1b49320987352e6c1b054acca0064f965a2c66992bed9a6f7d913f149 \ + --hash=sha256:2ecda9ec22edf38fa389369eaed8c3d37c05f3c54e69f69438dbb2cc1de1458b \ + --hash=sha256:4d77e67f65f98449e3fb83f795b5d0a8437aead2f874ca89c96576caf4be3af6 \ + --hash=sha256:5121af01cf911e70056c00d4b46d5e9b5d1415550038573d744138bacb59e6b8 \ + --hash=sha256:6b1b0eed82364b0e32c4ea0f221452d33e6bb17ae094d9f72aed9851812747ea \ + --hash=sha256:719757059f5a53fd0dde23f78cffeafcdd97b21c850ddb7ca684a3c1a1f122e2 \ + --hash=sha256:766cfd421c13e450feb340cd472a3ed9957d438727b7b4593ad7c76c5d2b0deb \ + --hash=sha256:7ef56fe650f50575bf843acde967b9c567687f3c22340941a899b7bc56e956a8 \ + --hash=sha256:7faba15ac005376e02a0384504e0243be3370ce010296a44a820feb342b505ab \ + --hash=sha256:8540f1e6205bd13ca0ce685581037219ca54a1b41a0a15d228c6c9b8ad5903d7 \ + --hash=sha256:87142215824be6ac05e2e8e2786eec307ccbc27c36723c3881959df654af6861 \ + --hash=sha256:915f887cf2682b66419b879423a2e072634aa7b7dce6f3ada4957cfced3f1e9a \ + --hash=sha256:a2d185dd1621757e70c3861cceffd5317ab4e7ed7eb09c82994828468527ade5 \ + --hash=sha256:a61efc018fd3eb317eeca31aba90ee9e7f26f22884a79b6c6ec715bf71bb62f1 \ + --hash=sha256:aca9b4ce85b152b5524ef7d88170efdff80dc0032aa8b75f9aaf7f3479ea95b4 \ + --hash=sha256:c4e7b79d83805475f0102008843f6eb45fd3bb0b2e88c774adab5fbaab27117d \ + --hash=sha256:c82304750f057167ff60d188df1d0cc1764ce9567eadf03e6a7443bcedd0b30b \ + --hash=sha256:c8d87c2134d871df96ecdea9cec7cbaab286dadab0f56476e57aaf9e8ac11550 \ + --hash=sha256:cde8adafa2365676f74a979744629589999093bc86e2484214f58e61df08902c \ + --hash=sha256:dc133a1569ee667b2a6ef56ce551084aeefd87a5acbc4736d336d1e2edc6cfc4 \ + --hash=sha256:ec6f1af59f6b5f3fc9678e2ea062d8377d22ac644f7844cb7a292910cf12ff44 \ + --hash=sha256:efa9f765dd09f9d0cdac651ffdf631ee59ec5dc6ee7a73e0c012ba9c52fbdf5b +grpcio==1.81.1 \ + --hash=sha256:0a37165cc80b1a368384b383e63a4c38116a10467ae44c904d2d7468c4470ec2 \ + --hash=sha256:12b7524c88d4026d3dcb7b0ebe16b6714f3b4af402ddd0f0639ab064a00c87c3 \ + --hash=sha256:1b22c80559854b789a01fd89e8929b3798a156c0829b5282a8939f33ad4115ad \ + --hash=sha256:1e123f9b37edb8375fd74130d1f69c944bbf0a7b06761ae7211154b8759e94d2 \ + --hash=sha256:2c2e2ae6867c2966b8daccc836d54a13218e0007e9a490aeb81dd05be64d22d7 \ + --hash=sha256:30e825f6848d9f18bba350ed6c75c1b02a0b5184474a31db9a32b1fa66fd8c79 \ + --hash=sha256:3ad74f8bb1a18963914c5452d289422830b39459e8776ebbcd207be1fbfb1d94 \ + --hash=sha256:428bec0161b48d8cf583c068591bc0016d0d9cfff52462b72b3884861ea768c5 \ + --hash=sha256:58ad1131c300d3c9b933802b3cc4dc69d380822935ba50b28703156ea826fbf7 \ + --hash=sha256:6282caffb41ec326d4cb67ca9cf53b739d1b2f975a2acb498c7418e9f7d9a416 \ + --hash=sha256:6fa10a767143a5e82e8eaab53918af0cd8909a57a27f8cb2288b80a613ac671b \ + --hash=sha256:766bc7c9a9c340342f4c864ccbda8e78111e4751f13b895812b9c148fb79e9d0 \ + --hash=sha256:78e29211f26da2fdd0e9c6d2b79f489476140cf7029b6a64808ade7ca4156a42 \ + --hash=sha256:819edbdcb42ab8598b494bcf0222684bbb7a3c772bd1b1f0be7e029a6063c28e \ + --hash=sha256:85b10a45b8993d195c4f3ff57025b8d1e11834909ee475c403bfa60cb4caefaf \ + --hash=sha256:8b39472beafc0bdcafc4c8c73ad082ebfdb449d566897a61e7acb4fa88089115 \ + --hash=sha256:8ea1936c26b99999b27479853039a7f34713f56c49375ad52b38535ec93a796c \ + --hash=sha256:98a07f9bf591e3a8919797bee1c53f026ba4acd587e5a4404c8e57c9ec36b2a5 \ + --hash=sha256:a185a04039df6cae8648bc8ab6d6fde7bf94f7188ecf7828e76ac52eef1e41d6 \ + --hash=sha256:a35009284d0d3d5c2c9601c164a911b8b4331608d98a9a66d47d97bb2f522b70 \ + --hash=sha256:a3acb384427816dd5d470f47e62137b87f74da694faa8a50147012cf40df276a \ + --hash=sha256:b10e1ff4756ed27d5a29d7fc79cfce7ef1ff56ad20025b89bac7cf79e09abbbe \ + --hash=sha256:b137f4bf3ada9dc44d411478decc6ff09a79ed30b306cd2abaa98408c3588137 \ + --hash=sha256:b259a04a737cb3496be0901328eb8b7552ed8df4865d8c8f1cf1bffcfc0776a3 \ + --hash=sha256:c261d74b1a945cf895a9d6eccd1685a8e837531beaab782da4d630a8d12deffb \ + --hash=sha256:c5bf2dc311127d91230cc79b92188c082634a06cf66c5234db49a43b910183b0 \ + --hash=sha256:d71d30f2d92f67d944631c523713934fee37292469e182ebcd2c1dd8a64ce53f \ + --hash=sha256:e64dd101d380a115cc5a0c7856788adb535f1a4e21fc543775602f8be95180ae \ + --hash=sha256:e8ca6a1fcdb2943c9cbc1804a1baf3acb6071d72a471591678ded84218006e14 \ + --hash=sha256:edb59506291b647a30884b1d51a599d605f40b20af4a7dc3d33786a47a31de60 \ + --hash=sha256:f9a0ebbe45c29b5e5866593c12b78bd9035f0f0f0d4bc8361680cd580d99db49 +gunicorn==26.0.0 \ + --hash=sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc \ + --hash=sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf +h11==0.16.0 \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 +h2==4.3.0 \ + --hash=sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1 \ + --hash=sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd +hiredis==3.4.0 \ + --hash=sha256:03374d663b0e025e4039757ef5fad02e3ff714f7a01e5b34c88de2a9c91359dc \ + --hash=sha256:04e54fc3bcecf8c7cb2846947b84baf7ce1507caba641bd23590c52fefade865 \ + --hash=sha256:14524fdc751e3960d78d848872576b5442b40baae3cac14fbab1ba7ac523891f \ + --hash=sha256:163d8c43e2706d23490532ea0de8736fc1493cfa52f0ee65f85b0f074f2fe017 \ + --hash=sha256:165e6405b48f9bd66ddb4ad52ce28b0c0041a0308654d7a0cb4357a1939134dc \ + --hash=sha256:1fb0a139cd52535f3e5a532816b5c36b3aea95817410fbf28ca4a676026347a5 \ + --hash=sha256:24751054bb11353016d242d09a4a902ecf8f25e3b56fe396cccb6f056fdda016 \ + --hash=sha256:258f820cdd6ee6be39ae6a8ea94a76b8856d34113de6604f63bc81327ef06240 \ + --hash=sha256:282c4310af72afbe18b07d416459f4febeaeb805a067a7df790136e0e550fcb2 \ + --hash=sha256:306aae11a52e495aaf0a14e3efcd7b51029e632c74b847bc03159e1e1f6db591 \ + --hash=sha256:35ab3653569b9867b8d8a3b4c0684a20dc769fe45d4666bedfe9a3391a61b30b \ + --hash=sha256:3774461209688790734b5db8934400a4456493fc1a172fb5298cc5d72201aceb \ + --hash=sha256:43004b0b48abc628dda1ac3ac4871e1326c126f8cd9f11164d61934d827d7a3b \ + --hash=sha256:4404c557fd49bcfe24dff41f1209e4221c76d1607df2fb2dfd39474b5b086dcb \ + --hash=sha256:44660a91e0fbc803c29b337c1a9194c8d7b4cd3a3868d28f747cbec2df165483 \ + --hash=sha256:452cff764acb30c106d1e33f1bdf03fa9d4a9b0a9c995d722d4d39c998b40582 \ + --hash=sha256:454236d2a5bd917daf38914ce363e71aeef41240e6800f4799e04ee82689bfd2 \ + --hash=sha256:4b8f52844cd260d7805eca55c834e3e06b4c0d5b53a4178143b92242c2517c0d \ + --hash=sha256:4f0e3536eea76c03435d411099d165850bc3c9d873efe62843b995027135a763 \ + --hash=sha256:5359caad5b57da0bce11d2880f22617ba3710f0866121a924745447848448034 \ + --hash=sha256:5f1ddfe6429f9adc0a8d705afbcd40530fddeafa919873ffbb11f59eda44dbb9 \ + --hash=sha256:696e0a2118e1df5ccacf8ecf8abe528cf0c4f1f1d867f64c34579bef77778cdb \ + --hash=sha256:74bcfb26189939daba2a0eb4bad05a6a30773bb2461f3d9967b8ced224bd0de9 \ + --hash=sha256:7f7fc1535f6e1a190089eae46dee25f0c6b72bb221d377be07092803b8208733 \ + --hash=sha256:82860f050aabd08c046f304eb57c105bb3d5a7370f79a4a0b74d2b771767cc13 \ + --hash=sha256:8aaaab18314fd25453b5cf59c8cdca4110e419455bcb4c0737d19d4151513e75 \ + --hash=sha256:8b3f1d03046765c0a83558bf1756811101e3947649c7ca22a71d9dc3c92929d1 \ + --hash=sha256:975a8e75a10425442037dd9c7abbaae31941c34328d9f01b1ca42d9db44ac31d \ + --hash=sha256:9e88048a66dfffec7a3f578f2a2a0fd907c75b5bd85b3c9184f76f0149ea399f \ + --hash=sha256:a315009b441a0105a373a9a780ebb1c6f7d9ead88ac6ea5f2a15791353c6f590 \ + --hash=sha256:a3796094f616f72976ff51e4dc1a016e753c0f9af5393b2df96920b6bae1e19b \ + --hash=sha256:afff0876dafad6d3bb446c907da2836954876243f6bb9d5e44915d175e424aa4 \ + --hash=sha256:bb44efa4fa3e3ed7779ad0ade3c08ed5d75ca7a6336893e9a4f2722093b4168a \ + --hash=sha256:bca175f02a2b0150ffe7f5dc8bf49c798f34d2c7024d17ace0ec97a7583560e3 \ + --hash=sha256:ccc5c660e31d788ca534a20f2ccb7a80b946b960e18ed4e1db950fcac122b405 \ + --hash=sha256:ccdb63363c82ea9cea2d48126bc8e9241437b8b3b36413e967647a17add59643 \ + --hash=sha256:d5c33eb2da5c9ccd281c396e1c618cfe6a91eb841e957f17d2fa520383b3111d \ + --hash=sha256:d95b602ab022f3505288ce51feaa48c072a62e57da55d6a7a38ecb8c5ad67d81 \ + --hash=sha256:da19331354433af6a2c54c21f2d70ba084933c0d7d2c43578ec5c5b446674ad5 \ + --hash=sha256:de3e2297a182253dfa4400883a9a4fb46d44946aed3157ea2da873b93e2525c4 \ + --hash=sha256:ed1dba2695f6de009c67d63b39ff978cb43b8a79362f697acedffb7743e50d21 \ + --hash=sha256:ee6b4beb79a71df67af15a8451366babc2687fcac674d5c6eacec4197e4ce8c1 \ + --hash=sha256:f3c67f39b112dc35f68d5b59ee111db6121f037d1a60cf3840ecffbb2ec5686b +hpack==4.2.0 \ + --hash=sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0 \ + --hash=sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986 +httpcore==1.0.9 \ + --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \ + --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8 +httptools==0.8.0 \ + --hash=sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683 \ + --hash=sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124 \ + --hash=sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c \ + --hash=sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09 \ + --hash=sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085 \ + --hash=sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376 \ + --hash=sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5 \ + --hash=sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8 \ + --hash=sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681 \ + --hash=sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999 \ + --hash=sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1 \ + --hash=sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d \ + --hash=sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d \ + --hash=sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d \ + --hash=sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745 \ + --hash=sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07 \ + --hash=sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2 \ + --hash=sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d \ + --hash=sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150 \ + --hash=sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7 \ + --hash=sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168 \ + --hash=sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a +httpx==0.28.1 \ + --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ + --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad +httpx-sse==0.4.3 \ + --hash=sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc \ + --hash=sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d +hyperframe==6.1.0 \ + --hash=sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5 \ + --hash=sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08 +idna==3.18 \ + --hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \ + --hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848 +inquirer==3.4.1 \ + --hash=sha256:60d169fddffe297e2f8ad54ab33698249ccfc3fc377dafb1e5cf01a0efb9cbe5 \ + --hash=sha256:717bf146d547b595d2495e7285fd55545cff85e5ce01decc7487d2ec6a605412 +jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 +jinxed==2.0.4 \ + --hash=sha256:58ae0a4a2930b51e9de162208c356b382f5e25b3bc0ad73e2013b02a62d84e8b \ + --hash=sha256:a92ac48923433c0a88577bb0479191788813fd5055ef17ff2b04a701a1b83f19 +jiter==0.16.0 \ + --hash=sha256:0542a7189c26920778658fc8fcf2af8bae05bae9924577f71804acef37996536 \ + --hash=sha256:0df91907609837f33341b8e6fe73b95991fdaa57caf1a0fbd343dffe826f386f \ + --hash=sha256:12288303c9844e61e1651d02a9a6f6633e47d39f897d6991d1427161ce6b746e \ + --hash=sha256:17ca7fae79f6d99cd9a042b75f917eaada7b895cfc7dd2ee3a16089dcaec7a85 \ + --hash=sha256:1878349266f8ee36ecb1375cc5ba2f115f35fd9f0a1a4119e725e379126647f7 \ + --hash=sha256:2a8e9e39cf083016137aa5cadafe3188adc2ba6ba1fbf1e5d18889ad3e9ad056 \ + --hash=sha256:2ed5738ae4af18271a51a528b8811b0cbfa4a1858de9d83359e4169855d6a331 \ + --hash=sha256:3126a5dbad56401989ac769aca0cb56005bfb3e2366eea0ca99d1a91c3c1ee03 \ + --hash=sha256:3a23f0e4f957e1be65752d2dfac9a5a06b1917af8dc85deb639c3b9d02e31290 \ + --hash=sha256:3c1fd2dbe1b0af19e987f03fe66c5f5bd105a2229c1aff4ab14890b24f41d21a \ + --hash=sha256:41977aa5654023948c2dae2a81cbf9c43343954bef1cd59a154dd15a4d84c195 \ + --hash=sha256:44d019fa8cdaf89bf29c71b39e3712143fdd0ac76725c6ef954f9957a5ea8730 \ + --hash=sha256:46add52f4ad47a08bfb1219f3e673da972191489a33016edefdb5ea55bfa8c48 \ + --hash=sha256:51d7b836acb0108d7c77df1742332cac2a1fa04a74d6dacec46e7091f0e91274 \ + --hash=sha256:560b2cf3fb03240cd34f27409a238547488708f05b7c3924f571a60422251ec7 \ + --hash=sha256:561926e0573ffe4a32498420a76d64b16c513e1ab413b9d28158a8764ac701e5 \ + --hash=sha256:5af7780e4a26bd7d0d989592bf9ef12ebf806b74ab709223ecca37c749872ea9 \ + --hash=sha256:5cf109d010b4b05a105afb3d43be36a21322d345ad3111e13d15f680afef0e5b \ + --hash=sha256:62c1b7fe1f77925acf5af68b6140b8810fa87dfd4dc0a9c8568ec2fa2a10429c \ + --hash=sha256:64c0203212098470032aabcde9356fc168f377aade3e43def61dfe17e92f2037 \ + --hash=sha256:64d613743df53199b1aa256a7d328340da6d7078aac7705a7db9d7a791e9cfd2 \ + --hash=sha256:659039cc50b5addcc35fcc87ae2c1833b7c0a8e5326ef631a75e4478447bcf84 \ + --hash=sha256:67c3bc1760f8c99d805dcab4e644027142a53b1d5d861f18780ebdbd5d40b72a \ + --hash=sha256:67fddeda1688f0cce2d2ae83ccf8a80f79936f2d2997d6cc2261f82fdb54a4d3 \ + --hash=sha256:6edb63a46e65a82c26800a868e49b2cac30dd5a4218b88d74bc2c848c8ad60bb \ + --hash=sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c \ + --hash=sha256:850ccb1d7eedb4200f4014b1c0e8a577de114fc3cd88faad646dcc9bc4bb12ad \ + --hash=sha256:8597d23c87f59294f83bcb6229b9ed1fccee13dbba967b46930d2f1759466fee \ + --hash=sha256:8d031aabecc4f1b6276adfb42e3aabb77c89d468bf616600e8d3a11328929053 \ + --hash=sha256:8fb8de1e23a0cb2a7f53c335049c7b72b6db41aa6227cdcc0972a1de5cb39450 \ + --hash=sha256:96e38eea538c8ddf853a35727c7be0741c76c13f04148ac5c116222f50ece3b3 \ + --hash=sha256:9c8a956fd72c2cf1e730d01ea080341f13aa0a97a4a33b51abebe725b7ae9ca9 \ + --hash=sha256:adff21bc78edfe086c15eb495b900306076de378dc2337c132401fc39bd79c91 \ + --hash=sha256:b72d0b2990ca754a9102779ac98d8597b7cb31678958562214a007f909eab78e \ + --hash=sha256:b7dc85ea77d4abbae8bad0d3538678aedee75bceec4e2f6c8dfb1c74772e5aa5 \ + --hash=sha256:baad945ed47f163ad833314f8e3288c396118934f94e7bbb9e243ce4b341a4fd \ + --hash=sha256:c22a488f7b9218e245a0025a9ba6b100e2e54700831cf4cf16833a27fba3ad01 \ + --hash=sha256:c4b4717bdb35ae456f831a6b08d01880fff399887a6bbc526a583a406e484eea \ + --hash=sha256:c682bea068a90b764577bdb78a60a4c1d1606daf9cd4c893832a37c7cc9d9026 \ + --hash=sha256:c90c0f63df322be920eda6ce622e3083d8906ba267f8220fe7873213b8b4430e \ + --hash=sha256:c9c53be232c2e206ef9cdbad81a48bfa74c3d3f08bcf8124630a8a748aad993e \ + --hash=sha256:d284fb8d94d5855d60c44fefcab4bf966f1da6fada73992b01f6f0c9bc0c6702 \ + --hash=sha256:d28bb3c26762358dadf3e5bf0bccd29ae987d65e6988d2e6f49829c76b003c09 \ + --hash=sha256:d5bf78d0e05e45cfdd66558893938d59afe3d1b1a824a202039b20e607d25a72 \ + --hash=sha256:d5f91b1c27fc22a57993d5a5cb8a627cb8ed4b10502716fac1ffbfe1d19d84e8 \ + --hash=sha256:dab907db06fc593645e73109acf4581ba5b548897d28b9348dc41ddc8343b2d3 \ + --hash=sha256:e34e97bda77eb63242a410243c071e28ac7e0d8c0948c5ee658498690a4b2f2f \ + --hash=sha256:e431cfc9caf44c1d5459ff77d4e64cbf85fddb6a35dad836a15c6a9ec23087c1 \ + --hash=sha256:eab2cd170150e70153de16896a1774e3a1dca80154c56b54d7a812c479a7165e \ + --hash=sha256:f17d61a28b4b3e0e3e2ba98490c70501403b4d196f78732439160e7fd3678127 \ + --hash=sha256:f4444a83f946605990c98f625cdd3d2725bfb818158760c5748c653170a20e0e +jq==1.11.0 \ + --hash=sha256:06819d14f18187379959f0ac5fdd1bcf7f452d84623a2945d7ed1d1ceeba8499 \ + --hash=sha256:0db188d73f2b6ad4e4f62653b2a4ea06dd99b790cc9a8aba6b617d9d0806aee0 \ + --hash=sha256:17755df4b65ad9f9021c43b90a04c02f771b32b8c429a0e7ff160a13229f787e \ + --hash=sha256:1bfeb6627f8dace23e0eaf7818ba4c5227e60bc2e843c9eafe895d59c0d274d1 \ + --hash=sha256:40489d775f77d9d8b4ec1f3ab1e977415e003c5065ad3befbe61ae80810bc381 \ + --hash=sha256:4a6d0cde00e6da44f49772c5f77b0efa1d11d5866b2af923fd94e2ffbdfae89c \ + --hash=sha256:5295d6d2a6c1de53f9ca1cd3211f5728e20c0be1e6456486a9e6b6016102e173 \ + --hash=sha256:54f896e878c89cef4c05aff53f822de62a08e91d08bad7cbf4f7e91b7a06a460 \ + --hash=sha256:594ebd007244e16b333bd2f35a5b766176be107ee99f9d92883a79d50439b93c \ + --hash=sha256:5e08573ee9a5448166f1e8f068ce05fba21db15adcebbaf2472729d36ec92a1e \ + --hash=sha256:62b8fd1b93ba3bad1f1051fa955ff675d076466c2e900c59afe2393bc09c49bc \ + --hash=sha256:67f1032e3a61b4e5dcdd4e390527b0000db521ac9872b64517c83c5f71ef8450 \ + --hash=sha256:680edbd5838fb04539469e39e3e147ae3890b90af3e727d6028e370f8a202b1c \ + --hash=sha256:77686477535191cdd2a01acfdcf3d67e71b4319edf33cab5bf5e383bbe147291 \ + --hash=sha256:7a8d080c00b5fc66bb9ee2876402b978b84a7108526b4c9affd704813a2add78 \ + --hash=sha256:824a295e62802a67a21f13e4b2d32a24ff5849f7bd435b042e68a9c598c8e778 \ + --hash=sha256:8bb8d244a6b11140c0908affbad621485788500d4236cf7c09b6f0087e991815 \ + --hash=sha256:9aba6e4a4e66a8d55f45137c7039dd56a65ad95ef4c1c1c208190971966429b9 \ + --hash=sha256:a8a67a52445535edffe10733b9da493134bfbf354acdbeb29d9ef28fd39938ac \ + --hash=sha256:c463edd4f45ff3e1923766a0c582a8e955bd889dd756e0ac6392d28f5ca144db \ + --hash=sha256:cd4376b62fc18e8c3f5334f388f92a48b148cf755610ad44d22958a41edcbc8d \ + --hash=sha256:d2ac5087d6d16929cf0f9918ed83b3d4c5d765bd160d3af125131bb124001d3a \ + --hash=sha256:d63c4437f256edb6c204481181d19e3e33f24781b1bfbab2db589af574567bed \ + --hash=sha256:e04a8c3b0b4d78cab658ec8cd1b34f7c9a711355ff6d8aa01a3b24955b3eb531 \ + --hash=sha256:e8f4f1fd9fd85416d978e4e0d8e3fb7603bdb7da87f5cd6dc5e94047b75a4813 \ + --hash=sha256:fe1c5facefd0f1197fb95cda8f31195487f431e4c4699cb0cb207efc47553504 +jsonpatch==1.33 \ + --hash=sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade \ + --hash=sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c +jsonpath-ng==1.8.0 \ + --hash=sha256:54252968134b5e549ea5b872f1df1168bd7defe1a52fed5a358c194e1943ddc3 \ + --hash=sha256:b8dde192f8af58d646fc031fac9c99fe4d00326afc4148f1f043c601a8cfe138 +jsonpointer==3.1.1 \ + --hash=sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900 \ + --hash=sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca +jsonschema==4.26.0 \ + --hash=sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326 \ + --hash=sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce +jsonschema-specifications==2025.9.1 \ + --hash=sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe \ + --hash=sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d +langchain-core==1.4.7 \ + --hash=sha256:7a825d77de0a3f39adbd9d09612a75e85527e14a52c1601089bcc062972d9f2b \ + --hash=sha256:bcadd51951140ecdcba98311dbd931ba5de02a5ba8a2288dad5069c1eea2a13d +langchain-mcp-adapters==0.3.0 \ + --hash=sha256:1af511a95e028d9546502e360f95698ae8b691dbc07981fc48170c2cb1ebd7a9 \ + --hash=sha256:fa6c9497015eb2807de5d0c341a36e1d2445cecbae1f4a24e922fc5b94f1a36c +langchain-ollama==1.1.0 \ + --hash=sha256:43ac83a6eacb0f43855810739794dd55019e0d9b17bdcf3ecb3b1991ac3b59dd \ + --hash=sha256:f776f56f6782ae4da7692579b94a6575906118318d1023b455d7207f9d059811 +langchain-openai==1.2.1 \ + --hash=sha256:a80732185030d4f453dda6c25feef46f645f665423fdffe38ae3edf1ac3c6c4d \ + --hash=sha256:ee4480b787706361b7125fad46930589a624df87aa158c6986ef1fad10d10675 +langchain-protocol==0.0.18 \ + --hash=sha256:70b53a86fbf9cedc863555effe44da192ab02d556ddbf2cf95b8873adcf41b5a \ + --hash=sha256:ec3e11782f1ed0c9db38e5a9ed01b0e7a0d3fba406faa8aef6594b73c56a63e6 +langgraph==1.2.0 \ + --hash=sha256:03fd5895a8d4b70db1ff63ebc3bacead29dd20cd794a8b1a483e7ec9018f7a65 \ + --hash=sha256:4a9baaf62afc5d5f63144a50095140a34b9aa9b7cea695d25326d564775348e7 +langgraph-checkpoint==4.1.1 \ + --hash=sha256:25d29144b082827218e7bc3f1e9b0566a4bb007895cd6cc26f66a8428739f56e \ + --hash=sha256:6c2bdb530c91f91d7d9c1bd100925d0fc4f498d418c17f3587d1526279482a25 +langgraph-prebuilt==1.1.0 \ + --hash=sha256:3c579cf6eed2d17f9c157c2d0fcaddcd8688524e7022d3b22b37a3bf4589d528 \ + --hash=sha256:51e311747d755b751d5c6b39b0c1446124d3a7643d2515017e6714b323508fc9 +langgraph-sdk==0.3.15 \ + --hash=sha256:29e805003d2c6e296823dd71992610976fd0428cefaa8b3304fd91f2247037de \ + --hash=sha256:3838773acf7456d158165385d49f48f1e856f28b56ccd99ea139a8f27004815d +langsmith==0.9.7 \ + --hash=sha256:40f8a66a466a7abd991a9df1b50de0a9d30c48ee0d3da46ce00516b68e41c9d7 \ + --hash=sha256:a5ff9179b77eb0c3a0129294d30924eab13e51e2720f374372dbbc014c892911 +mako==1.3.12 \ + --hash=sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9 \ + --hash=sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a +markdown-it-py==4.2.0 \ + --hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \ + --hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a +markupsafe==3.0.3 \ + --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ + --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \ + --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \ + --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \ + --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \ + --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \ + --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \ + --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \ + --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \ + --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \ + --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \ + --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \ + --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \ + --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \ + --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \ + --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \ + --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \ + --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \ + --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \ + --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \ + --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \ + --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \ + --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \ + --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \ + --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \ + --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \ + --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \ + --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \ + --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \ + --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \ + --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \ + --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \ + --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \ + --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \ + --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \ + --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \ + --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \ + --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \ + --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \ + --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \ + --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \ + --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \ + --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \ + --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \ + --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a +mcp==1.30.0 \ + --hash=sha256:445414625fce5c295faa505bb11bacece661ab6f4028d57c935db57820b7a3e4 \ + --hash=sha256:666edb5009503e1047c9d60346a756f94b261f05cc2625f23d41c728ffc484d0 +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba +msgpack==1.2.1 \ + --hash=sha256:020e881a764b20d8d7ca1a54fc01b8175519d108e3c3f194fddc200bda95951a \ + --hash=sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647 \ + --hash=sha256:0a70e3cf2804a300d921bb0940426e35f4e489a23adfb77a808892241db0a064 \ + --hash=sha256:0c0d9802354507bcba62af19c17918e3eb437cc25e6f50657d511b5856a77aac \ + --hash=sha256:1233ee2dd0cefba127583de50ea654677277047d238303521db35def3d7b2e7c \ + --hash=sha256:146ee4e9ce80b365c6d4c47073da9da7bcec473e58194ceee5dd7620ace77e06 \ + --hash=sha256:29a3f6e9667868429d8240dfd063ea5ffdc1321c13d783aa23827a38de0dcb22 \ + --hash=sha256:2eda0b7ebb1283a98d3e4492ac933c8af6aff59fd3df1c3ed024f536af4b1dc8 \ + --hash=sha256:2ef59c659f289eddf8aa6623823f19fa2f40a4029266889eac7a2505dd210c35 \ + --hash=sha256:33f14fba63278b714efe6ad07e50ea5f03d91537aa6a1c5f1ceca4cf44013ca9 \ + --hash=sha256:4202c74688ca06591f78cb18988228bd4cca2cc75d57b60008372892d2f1e6e6 \ + --hash=sha256:4227224aaec8f7fbcbfbd4272319347b2bb4030366502600f8c45588c5187b07 \ + --hash=sha256:491cc39455ca765fad51fb451bf2915eb2cf41192ab5801ce8d67c1d614fe056 \ + --hash=sha256:5c24aa15d5963051e1a5c62b12c50cd705992502b5ec1f3bece6046f33c9fc24 \ + --hash=sha256:60926b75d00c8e816ef98f3034f484a8bc64242d66839cef4cf7e503142316a0 \ + --hash=sha256:67f6dd22fa72a93752643f07889796d62739a13415ee630169a8ce764f86cf9f \ + --hash=sha256:6d09badf350af2be9d189184e04e64cf54ad93569ab3d96fca58bd3e84aad707 \ + --hash=sha256:6ee967f7c7e1df2890c671ff2ee51a28ded0efc95da3e507176dee881ce36c66 \ + --hash=sha256:779197a6513bab3c3632265e3d0f7cb3227e62510841a6f34f1eaa37efbb345e \ + --hash=sha256:787c9bebb5833e8f6fc8abca3c0597683d8d87f56a8842b6b89c75a5f3176e2d \ + --hash=sha256:7d31c0ac0c640f877804c67cb2bc9f4e23dc2db97e96c2e67fa27d38283b41f8 \ + --hash=sha256:85f57e960d877f2977f6430896191b04a21f8901b3b4baf2e4604329f4db5402 \ + --hash=sha256:8b267ce94efb76fbd1b3373511420074ee3187f0f7811bf394531de13294735a \ + --hash=sha256:8ff92d7feeaf5bc26c51495b69e2f99ed97ab79346fb6555f44be7dd2ac6503b \ + --hash=sha256:91054a783328e0ea7954b8771095705c8d2243b814743fbaadf14552c9c52c5d \ + --hash=sha256:a28d076ca7c82b9c8728ad90b7147489449557038bed50e4241eb832395169b4 \ + --hash=sha256:aded5bdf32609dc7987a49bbbd15a8ef096193f96dd8bbeb791de729e650acf5 \ + --hash=sha256:afc5febcd4c99effbc02b528e49d6fd0760b2b7d48c05239e345a5fa6e743d9a \ + --hash=sha256:d3567748a5107cb40cdf66a275430c2f87c07777698f4bfd25c35f44d533258c \ + --hash=sha256:dc871b997a9370d855b7394465f2f350e847a5b806dd38dcc9c989e7d87da155 \ + --hash=sha256:e3dc2feb0876209d9c38aa56cb1de169bd6c4348f1aa48271f241226590993e6 \ + --hash=sha256:e4f1d0f8f98ade9634e01fb704a408f9336c0a8f1117b369f5db83dc7551d8b1 \ + --hash=sha256:f02cf17a6ca1abe29b5f980644f7551f94d71f2011509b26d8625ce038f0df64 \ + --hash=sha256:f310233ef7fb9c14e201c93639fe5f5260b005f56f0b29048e999c30935596cc +oauthlib==3.3.1 \ + --hash=sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9 \ + --hash=sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1 +ollama==0.6.2 \ + --hash=sha256:3ad7daab28e5a973445c36a73882a3ef698c2ebb00e21e308652741577509f7d \ + --hash=sha256:936d55daa684f474364c098611c933626f8d6c7d67065c5b7ae0c477b508b07f +openai==2.44.0 \ + --hash=sha256:0a2a3ab2e29aeda368700f662ff9ba0f9df17ba4c54577a64e08b8115a3cc0ad \ + --hash=sha256:68a5a5ffad82b8ff7d451c437529fb64f7c3b8123aaf0c021966a882d9e3947d +opentelemetry-api==1.43.0 \ + --hash=sha256:107d0d03857ea8fc7c5fcbbbd83f800c281f0d560553d61c1d675fccfd1761c1 \ + --hash=sha256:20acf45e9b21851926835292e4045d290acade1edd2ff3de86d2f069687ba1fd +opentelemetry-exporter-otlp-proto-common==1.43.0 \ + --hash=sha256:123c3f9cc87218562490c63b36f497bf3a722faf174a515d1443f31ababa6264 \ + --hash=sha256:c4e32ba6d6b13bdb2b8f6764c4fd28d00192826561aa04f6d14eedfce7ac076f +opentelemetry-exporter-otlp-proto-grpc==1.43.0 \ + --hash=sha256:1b3e0627daa9bc21884d4a13946807c255eb558bfe5bdd543dffb6f4c9faee0d \ + --hash=sha256:6a10d1feacffffda19acacbf277b736094b1e2f4dbb98c90ccb2c6e1962e2ec6 +opentelemetry-exporter-otlp-proto-http==1.43.0 \ + --hash=sha256:647f603aa8efdbdb4dbff842e0729d0406a6fff26b295a72d3d60e7d963b2610 \ + --hash=sha256:fa8a42bb7d00ee5391f4c0b04d8e6a46c03caa437903296ab73a81dc11ba118f +opentelemetry-proto==1.43.0 \ + --hash=sha256:224778df17e1f3fafeaaa21d874236ca5f6ffc2f86e0899298ec7351aac27924 \ + --hash=sha256:c58f1f7ef84bc7dc2834016c0c37fe0081dde7ca9f6339be1970fbf9cdaaa90d +opentelemetry-sdk==1.43.0 \ + --hash=sha256:d1323a547c1ce69d6a069a17a44b7da82bb8b332051ecb074041f87642c86823 \ + --hash=sha256:d8187c81c162df9913e4003dd6485f7390d9a24fc17026ec7387b8b8218b08e9 +opentelemetry-semantic-conventions==0.64b0 \ + --hash=sha256:72f76fb2d1582d9d033dd1fcd84532e961e6ff3d90d24ba6fabc72975a83864c \ + --hash=sha256:ea77e85e354b8f604ddbe5f3d9135216f982fa4d77e5859ac30f6d8a50505aa6 +orjson==3.11.9 \ + --hash=sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4 \ + --hash=sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62 \ + --hash=sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979 \ + --hash=sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a \ + --hash=sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1 \ + --hash=sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180 \ + --hash=sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980 \ + --hash=sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697 \ + --hash=sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09 \ + --hash=sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd \ + --hash=sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470 \ + --hash=sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877 \ + --hash=sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd \ + --hash=sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe \ + --hash=sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97 \ + --hash=sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c \ + --hash=sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5 \ + --hash=sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021 \ + --hash=sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362 \ + --hash=sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206 \ + --hash=sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218 \ + --hash=sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9 \ + --hash=sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f \ + --hash=sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02 \ + --hash=sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be \ + --hash=sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2 \ + --hash=sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa \ + --hash=sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c \ + --hash=sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10 \ + --hash=sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677 \ + --hash=sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4 \ + --hash=sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624 \ + --hash=sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49 \ + --hash=sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0 \ + --hash=sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c \ + --hash=sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2 \ + --hash=sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61 \ + --hash=sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882 \ + --hash=sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff \ + --hash=sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254 \ + --hash=sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f \ + --hash=sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32 \ + --hash=sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff \ + --hash=sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291 \ + --hash=sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f \ + --hash=sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81 +ormsgpack==1.12.2 \ + --hash=sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d \ + --hash=sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9 \ + --hash=sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172 \ + --hash=sha256:34d5b28b3570e9fed9a5a76528fc7230c3c76333bc214798958e58e9b79cc18a \ + --hash=sha256:3708693412c28f3538fb5a65da93787b6bbab3484f6bc6e935bfb77a62400ae5 \ + --hash=sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d \ + --hash=sha256:43013a3f3e2e902e1d05e72c0f1aeb5bedbb8e09240b51e26792a3c89267e181 \ + --hash=sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7 \ + --hash=sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc \ + --hash=sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685 \ + --hash=sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355 \ + --hash=sha256:73670ed0375ecc303858e3613f407628dd1fca18fe6ac57b7b7ce66cc7bb006c \ + --hash=sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7 \ + --hash=sha256:7c8b1667a72cbba74f0ae7ecf3105a5e01304620ed14528b2cb4320679d2869b \ + --hash=sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e \ + --hash=sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33 \ + --hash=sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e \ + --hash=sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709 \ + --hash=sha256:bd5f4bf04c37888e864f08e740c5a573c4017f6fd6e99fa944c5c935fabf2dd9 \ + --hash=sha256:c2be829954434e33601ae5da328cccce3266b098927ca7a30246a0baec2ce7bd \ + --hash=sha256:c6a4c34ddef109647c769d69be65fa1de7a6022b02ad45546a69b3216573eb4a \ + --hash=sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258 \ + --hash=sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4 \ + --hash=sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6 \ + --hash=sha256:df6961442140193e517303d0b5d7bc2e20e69a879c2d774316125350c4a76b92 \ + --hash=sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6 \ + --hash=sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1 \ + --hash=sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd +packaging==26.2 \ + --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \ + --hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661 +parse==1.22.1 \ + --hash=sha256:20f0925a46f06602485ac90d751764d0697fd8455aaa97489ba8953a4b66de32 \ + --hash=sha256:d3a4740ec3da338e2b258b2d69741b731eadfddca59e24a14bc4ee5fce38c911 +prometheus-client==0.25.0 \ + --hash=sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28 \ + --hash=sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1 +prometheus-fastapi-instrumentator==8.0.2 \ + --hash=sha256:3c252e748151768a7aefd66824a04a870144f71de48a67aed211749a9ca2a548 \ + --hash=sha256:746002ec1e2c58b93f61444e1d104de959a9463a6a3f1c8909ac3757e16c3866 +protobuf==6.33.6 \ + --hash=sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326 \ + --hash=sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901 \ + --hash=sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3 \ + --hash=sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a \ + --hash=sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135 \ + --hash=sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3 \ + --hash=sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2 \ + --hash=sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593 +psutil==7.2.2 \ + --hash=sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372 \ + --hash=sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9 \ + --hash=sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63 \ + --hash=sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979 \ + --hash=sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b \ + --hash=sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee \ + --hash=sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312 \ + --hash=sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b \ + --hash=sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9 \ + --hash=sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e \ + --hash=sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc \ + --hash=sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea \ + --hash=sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988 \ + --hash=sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486 \ + --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 +psycopg==3.3.4 \ + --hash=sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a \ + --hash=sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc +psycopg-binary==3.3.4 ; implementation_name != 'pypy' \ + --hash=sha256:018fbed325936da502feb546642c982dcc4b9ffdea32dfef78dbf3b7f7ad4070 \ + --hash=sha256:0579252a1202cd73e4da137a1426e2dae993ae44e757605344282af3a082848c \ + --hash=sha256:136f199a407b5348b9b857c504aff60c77622a28482e7195839ce1b51238c4cc \ + --hash=sha256:13a7f380824c35896dcac7fe0f61440f7ca49d6dc73f3c13a9a4471e6a3b302e \ + --hash=sha256:17a21953a9e5ff3a16dab692625a3676e2f101db5e40072f39dbee2250194d68 \ + --hash=sha256:1dc1f79fd16bb1f3f4421417a514607539f17804d95c7ed617265369d1981cae \ + --hash=sha256:22cdbf5f91ef7bb91fe0c5757e1962d3127a8010256eefd9c61fcaf441802097 \ + --hash=sha256:26df2717e59c0473e4465a97dfb1b7afebaa479277870fd5784d1436470db47c \ + --hash=sha256:276904e3452d6a23d474ef9a21eee19f20eed3d53ddd2576af033827e0ba0992 \ + --hash=sha256:41f2ec0fea529832982bcb6c9415de3c86264ebe562b77a467c0fbcd7efbba8d \ + --hash=sha256:494ca54901be8cf9eb7e02c25b731f2317c378efa44f43e8f9bd0e1184ae7be4 \ + --hash=sha256:580ae30a5f95ccd90008ec697d3ed6a4a2047a516407ad904283fa42086936e9 \ + --hash=sha256:5ab28a2a7649df3b72e6b674b4c190e448e8e77cf496a65bd846472048de2089 \ + --hash=sha256:5c4ab71be17bdca30cb34c34c4e1496e2f5d6f20c199c12bad226070b22ef9bf \ + --hash=sha256:612a627d733f695b1de1f9b4bd511c15f999a5d8b915d444bbd7dd71cf3370da \ + --hash=sha256:6402a9d8146cf4b3974ded3fd28a971e83dc6a0333eb7822524a3aa20b546578 \ + --hash=sha256:71e55ccbdfae79a2ed9c6369c3008a3025817ff9d7e27b32a2d84e2a4267e66e \ + --hash=sha256:7465bfe6087d2d5b42d4c53b9b11ca9f218e477317a4a162a10e3c19e984ba8e \ + --hash=sha256:75a9067e236f9b9ae3535b66fe99bddb33d39c0de10112e49b9ab11eee53dc31 \ + --hash=sha256:773d573e11f437ce0bdb95b7c18dc58390494f96d43f8b45b9760436114f7652 \ + --hash=sha256:77df19583501ea288eaf15ac0fe7ad01e6d8091a91d5c41df5c718f307d8e31b \ + --hash=sha256:7f7668f30b9dd5163197e5cbf4e0efd54e00f0a859cc566ce56cfc31f4054839 \ + --hash=sha256:ab8cca8ef8fb1ccf5b048ae5bd78ba55b9e4b5d472e3ce5ca39ff4d2a9c249e4 \ + --hash=sha256:b56b603ebcea8aa10b46228b8410ba7f13e7c2ee54389d4d9be0927fd8ce2a70 \ + --hash=sha256:b6f5a29e9c775b9f12a1a717aa7a2c80f9e1db6f27ba44a5b59c80ac61d2ffcf \ + --hash=sha256:c677c4ad433cb7150c8cd304a0769ae3bcfbe5ea0676eb53faa7b1443b16d0d3 \ + --hash=sha256:cffc3408d77a27973f33e5d909b624cce683db5fc25964b02fe0aae7886c1007 \ + --hash=sha256:dbfdb9b6cc79f31104a7b162a2b921b765fcc62af6c00540a167a8de47e4ed38 \ + --hash=sha256:e2631da29253a98bd496e6c4813b24e09a4fe3fb2a9e88513305d6f8747cce95 \ + --hash=sha256:e7510c37550f91a187e3660a8cc50d4b760f8c3b8b2f89ebc5698cd2c7f2c85d \ + --hash=sha256:eb05ee1c2b817d27c537333224c9e83c7afb86fe7296ba970990068baf819b16 \ + --hash=sha256:ee17a2cf4943cde261adfad1bbc5bf38d6b3776d7afff74c7cabcbeaeb08c260 \ + --hash=sha256:fbd1d4ed566895ad2d3bf4ddfd8bae90026930ddf29df3b9d91d32c8c47866a7 +psycopg-c==3.3.4 ; implementation_name != 'pypy' \ + --hash=sha256:ed8106128b2d04359c185fc9641b4409abfce4d0b6fb1d1ff6800646e27f1a22 +pycparser==3.0 ; implementation_name != 'PyPy' \ + --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ + --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 +pydantic==2.13.4 \ + --hash=sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba \ + --hash=sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6 +pydantic-core==2.46.4 \ + --hash=sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0 \ + --hash=sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262 \ + --hash=sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda \ + --hash=sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0 \ + --hash=sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b \ + --hash=sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594 \ + --hash=sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2 \ + --hash=sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c \ + --hash=sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d \ + --hash=sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398 \ + --hash=sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d \ + --hash=sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3 \ + --hash=sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7 \ + --hash=sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4 \ + --hash=sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df \ + --hash=sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2 \ + --hash=sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7 \ + --hash=sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3 \ + --hash=sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a \ + --hash=sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987 \ + --hash=sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e \ + --hash=sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712 \ + --hash=sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008 \ + --hash=sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1 \ + --hash=sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be \ + --hash=sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea \ + --hash=sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292 \ + --hash=sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33 \ + --hash=sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826 \ + --hash=sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7 \ + --hash=sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d \ + --hash=sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc \ + --hash=sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3 \ + --hash=sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b \ + --hash=sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04 \ + --hash=sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b \ + --hash=sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce \ + --hash=sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4 \ + --hash=sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a \ + --hash=sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f \ + --hash=sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894 \ + --hash=sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9 \ + --hash=sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76 \ + --hash=sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb \ + --hash=sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109 \ + --hash=sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848 \ + --hash=sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526 \ + --hash=sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458 \ + --hash=sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b \ + --hash=sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf \ + --hash=sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4 \ + --hash=sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd \ + --hash=sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30 \ + --hash=sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983 \ + --hash=sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7 \ + --hash=sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c \ + --hash=sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3 \ + --hash=sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89 \ + --hash=sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76 \ + --hash=sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4 \ + --hash=sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c \ + --hash=sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e +pydantic-settings==2.14.2 \ + --hash=sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440 \ + --hash=sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f +pygithub==2.9.1 \ + --hash=sha256:2ec78fca30092d51a42d76f4ddb02131b6f0c666a35dfdf364cf302cdda115b9 \ + --hash=sha256:59771d7ff63d54d427be2e7d0dad2208dfffc2b0a045fec959263787739b611c +pygments==2.20.0 \ + --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ + --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 +pyjwt==2.15.0 \ + --hash=sha256:7a3742debf6b879e912dbb9819ceec1594be812452b78c5f2e2dfc56564954f8 \ + --hash=sha256:b11c5f9791d7bf51c2b39a81ed669f6b2dbbd669df2942f6c60167e9e3d1abe4 +pynacl==1.6.2 \ + --hash=sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c \ + --hash=sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4 \ + --hash=sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130 \ + --hash=sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87 \ + --hash=sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa \ + --hash=sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0 \ + --hash=sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e \ + --hash=sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c \ + --hash=sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0 \ + --hash=sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c \ + --hash=sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577 \ + --hash=sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6 \ + --hash=sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465 +python-dotenv==1.2.2 \ + --hash=sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a \ + --hash=sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3 +python-json-logger==4.1.0 \ + --hash=sha256:132994765cf75bf44554be9aa49b06ef2345d23661a96720262716438141b6b2 \ + --hash=sha256:b396b9e3ed782b09ff9d6e4f1683d46c83ad0d35d2e407c09a9ebbf038f88195 +python-multipart==0.0.32 \ + --hash=sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e \ + --hash=sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23 +pywin32==312 ; sys_platform == 'win32' \ + --hash=sha256:17948aeadbdb091f0ced6ef0841620794e68327b94ee415571c1203594b7215c \ + --hash=sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831 \ + --hash=sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950 \ + --hash=sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c \ + --hash=sha256:b2200a054ca6d6625c4842fc56a4976a4b47f96b73dbe5538c3f813a80359f47 \ + --hash=sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc \ + --hash=sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9 \ + --hash=sha256:d11417d84412f859b722fad0841b3614459ed0047f7542d8362e77884f6b6e8a \ + --hash=sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b +pyyaml==6.0.3 \ + --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \ + --hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \ + --hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \ + --hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \ + --hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \ + --hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \ + --hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \ + --hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \ + --hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \ + --hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \ + --hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \ + --hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \ + --hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \ + --hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \ + --hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \ + --hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \ + --hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \ + --hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \ + --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \ + --hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \ + --hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \ + --hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \ + --hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \ + --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \ + --hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \ + --hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \ + --hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \ + --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \ + --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \ + --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0 +readchar==4.2.2 \ + --hash=sha256:92daf7e42c52b0787e6c75d01ecfb9a94f4ceff3764958b570c1dddedd47b200 \ + --hash=sha256:e3b270fe16fc90c50ac79107700330a133dd4c63d22939f5b03b4f24564d5dd8 +redis==8.0.1 \ + --hash=sha256:47daa35a058c23468d6437f17a8c76882cb316b838ef763036af99b96cedd743 \ + --hash=sha256:afc5a7a2f5a084f5b1880dec548dd45be17db7e43c82a30d84f952aefb05cfb0 +referencing==0.37.0 \ + --hash=sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 \ + --hash=sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8 +regex==2026.6.28 \ + --hash=sha256:03376d60b6a11aecb88a79fa2be06b40faa01c6693bc31ef69435cd4818b9463 \ + --hash=sha256:0c31665c0deb5c111557a1cac8c27bd5629e2f9e7fd5058900a03576c33b601c \ + --hash=sha256:0e6cb5a61486f9062397d2e189573b39d38ecfaed698fd9fb6e2756a8ebb8762 \ + --hash=sha256:11251768cc23f097dd61b18f67966e70f74da822784d17e12a444eb6b29d4288 \ + --hash=sha256:1484bdd6fba28422df9b5ebb04055b2e1b680e8e4f08490bb21ff0f3cc50d0ab \ + --hash=sha256:17c077586770f67e05bbffeba07fbee6b2b22244f4d4caf8d94e59d574befe04 \ + --hash=sha256:17eddca4e8ea9af0b5739314776cdf0172a49731ab61f2e1ea66e066ddd46c97 \ + --hash=sha256:189dbf9fc4252d9f1352bf4bd1bef885edb6cc4b7341df202a65f821aaa3891c \ + --hash=sha256:1e164ace4dbab5c6ad4a4ac7c41a2638fe226d0c770a86f2eb041f594bac6ee7 \ + --hash=sha256:28f9e6c28f9b90f6f784595a33240a57e181e61b6ee3dc259b25c61e356d1aa3 \ + --hash=sha256:2e27727fba075f1e4409416d2f537d4c30fc11f012ea507f7bd74d3e19ecb57a \ + --hash=sha256:3169a3159e4d99d9ae85ff0ed90ef3b8906cc3152653b6078b842ace6c8f72c3 \ + --hash=sha256:37294d3d7ddb64c7e89184b2894e0f8f0a19c514bc59513d71fe692c3a8d5fc6 \ + --hash=sha256:378a71d861fc7c8806b04ac5b133d53c0e774f92f5d9663a539872d3fa2b0417 \ + --hash=sha256:3bd630a8dba06b55254ea5ee862194edab52ec783100d2ef1cd15a9c512fee27 \ + --hash=sha256:3cb4b6c5cb3060cc31efdc1fbb27c25fb9b29044afd87e40601a1c4d9db54342 \ + --hash=sha256:3f15020f0b69cafe57baa067ff65b29acef68ff6b1670a53bef1ca11d708e02d \ + --hash=sha256:3f6316f258bc7e6c9c2acbe9954947bbd397a81be3742a637a555f1855d6618d \ + --hash=sha256:4303ebe16b74eeb3fe2715745023266fea92fd44a23f3e7bb2fb48c7a7bbc195 \ + --hash=sha256:4cc199874ecd6267a49b111052250825bfe19b5101b23b2ba80f54efa3e0994e \ + --hash=sha256:4d80c798b0eec6ea3d45f8816a1e8886c5664615d347d89e8c075b576a1b5a5d \ + --hash=sha256:4dfd1331c49233998d84fc5f1f4436cf7a435a7655f6cf0f490229bb5c7254e5 \ + --hash=sha256:56b856b70b96c381d837f609eee442a1bd320cd2159f5c294b679552fb1a7eaf \ + --hash=sha256:5977295b0a74e8241df8a4b3b27b12412a831f6fa32ee8b755039592cd768c3d \ + --hash=sha256:5f2c1682b67ad5d2376498f2a5a2a8f782fa2e4a06d0465b5e357799806e8a20 \ + --hash=sha256:64e142eb55e84868087da1375d7c36ff97d55010951849f515322a91d5fef1b4 \ + --hash=sha256:695873e0ea8d3815ea9e92e2c68faf039cc450e2c0a62a31afe2049eb11be767 \ + --hash=sha256:6bf295f2c59de77d1ea7de053607ae4dc9ceb3d57bbb6c7ec51ef4acc4ccff94 \ + --hash=sha256:6de82c268e5d101ee9e3ffd869924aa9a371e3a21e752cf4fa17b6ce50d219f7 \ + --hash=sha256:700fc6a7844bb2c4149292ac79d1df8841a00acd4d45cd32c1ebc7bcc1fd0da8 \ + --hash=sha256:80c7adf1ef647f6b1e8aa2ca280e517174cd08bdf7a2e412cdfb68bd6a0917cb \ + --hash=sha256:81cc5793ad33a10444445e8d29d3c73e752c8fb2e120772d70fcb6d41df40fe1 \ + --hash=sha256:8b92366d9c8bba9642989534073662abdd9b41faf7603a7ae71597833f3b88f0 \ + --hash=sha256:90581684565a93f7258af1e5d3f41ef20d7d7c61f2a428183a342bcb65485e38 \ + --hash=sha256:9277a4c6503390aa39cb4483b87ec0384faee0850a23b5cea33d008b5d8d83f1 \ + --hash=sha256:9c26a47770d30a0f85c01e261d2a3ebc342c4af6fd666dbd8c1fe4cbf3adf726 \ + --hash=sha256:9cfcd4b0bdcf768c498415c170d1ed2a25a99bf0b65fa253bbd02f68ceba6475 \ + --hash=sha256:a043f5770e82283a22aed4cefef1a4e0f9dd8fd7184cb6ce0ad2e579e2134a9e \ + --hash=sha256:a361feeaf1b6ba1df060f2ff5c5947092edf537a35ce78e76387ac56d3e0f4a4 \ + --hash=sha256:a71b51dd08b9b62f055fafab3dee8af8bd2ec81b373a44caef18d6c5ca28f43a \ + --hash=sha256:abb4daabe7be63273787a62dfd6164dadf8f7a63fbec3d2730e5e5e7126d858c \ + --hash=sha256:ad5c67786145ec28a71a267d9f9d92bdc8d70d65541eea852c253f520a01f918 \ + --hash=sha256:ad73ecf20c1ef5c975639f8bf845a9370fcf7dada7edc1e3b0bca20e2f8202f6 \ + --hash=sha256:b15859e3908544fb99cf47341dcf0bfd089147d258c4c4d8a29e5b087f8085cb \ + --hash=sha256:b295a83426e0e44e9e60fde99789e181bd26788a1890ae7fe2a24c69bb6246ca \ + --hash=sha256:b77207e3cee13086f1906a6a2a12b41244c577e8ad9370d4b35ae1d548d354f3 \ + --hash=sha256:b916a10431494ef4b4d62c6c89cab6426af7873125b8cd6c15811bf5fc58eec8 \ + --hash=sha256:bf54bc693fc4e0530e666ba5ec4bcba14dbe8f66b7cfc15c27317d1a6e40b9a5 \ + --hash=sha256:bfc9677982c914d9085b8e1c3b3ae6e88f139fb56531c2416d6c8f338093c22b \ + --hash=sha256:c4ac65f3e3a99fd8f3a4a74e7a6610acd1ce9dfe9b8a03d346a4922380d68aeb \ + --hash=sha256:c91487a917edd48a1ea646fdf60d7936d304f0e686fa7ea8326e47efca51d816 \ + --hash=sha256:cadea12805a1bce0b091c302b814207be26fb60a9c0e7f9ad2f9e21790a429fe \ + --hash=sha256:d98b639046e51c5de64d9f77351532105e99ca271cb6f7640e1f903d6ab63032 \ + --hash=sha256:e128feaf65bf3d9eb91bec92322a8f7e4835e9c798f3e9ea4b69f4def85620e3 \ + --hash=sha256:e18225243250a1f7d7e5e5d883f3b96465cd79031acf5c6db902b7025f2125d9 \ + --hash=sha256:e4466b8641e00c697aab5a73150150d2b2ea96b131c595691f42031abafd9f4d \ + --hash=sha256:e5efbc1af38f97e300d43028e5a92e752d924bcfb7f465d8669d5d5a6e78c233 \ + --hash=sha256:e86e91a2664f44c3a4e363a7d78fb17c27d5046882e30ea5a877f5e89b28d2ba \ + --hash=sha256:ec2b2ad00ab8c16a2798cc8db80c53c4d5b8b3a2441f6cbaef06625f5ca25854 \ + --hash=sha256:ecd1638b1c2db1f2d01c182a4b0d3e2e88b0e99910320a745c1727ee3638ddab \ + --hash=sha256:f1758df6fdd8c800620a5638958720e8a635e1da49a2f09df2dd63e94a24ec4a \ + --hash=sha256:f1da438e739765c3e85175ede05816cbede3caaacb1e0680568bda6119bfdfca \ + --hash=sha256:f5fbaef40c3e9282ccee4b075f5600a0d858aa0c34147732f1baa69c8188a95d \ + --hash=sha256:f74675ab76ab1d005ffba4dee308e53e89efc22be6e9f9fae5b539a3f81bdff2 \ + --hash=sha256:fbd2ded482bf99e6651992bbfcde460272724d4bbc49ef3d6b46d9312867ec84 +requests==2.34.2 \ + --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \ + --hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed +requests-oauthlib==2.0.0 \ + --hash=sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36 \ + --hash=sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9 +requests-toolbelt==1.0.0 \ + --hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \ + --hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 +rich==15.0.0 \ + --hash=sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb \ + --hash=sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36 +rpds-py==2026.6.3 \ + --hash=sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680 \ + --hash=sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9 \ + --hash=sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804 \ + --hash=sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4 \ + --hash=sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97 \ + --hash=sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6 \ + --hash=sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96 \ + --hash=sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975 \ + --hash=sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9 \ + --hash=sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127 \ + --hash=sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f \ + --hash=sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa \ + --hash=sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171 \ + --hash=sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c \ + --hash=sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223 \ + --hash=sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4 \ + --hash=sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885 \ + --hash=sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f \ + --hash=sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7 \ + --hash=sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed \ + --hash=sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf \ + --hash=sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f \ + --hash=sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24 \ + --hash=sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a \ + --hash=sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41 \ + --hash=sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc \ + --hash=sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4 \ + --hash=sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7 \ + --hash=sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261 \ + --hash=sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6 \ + --hash=sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93 \ + --hash=sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7 \ + --hash=sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c \ + --hash=sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0 \ + --hash=sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3 \ + --hash=sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826 \ + --hash=sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb \ + --hash=sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80 \ + --hash=sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90 \ + --hash=sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7 \ + --hash=sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0 \ + --hash=sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822 \ + --hash=sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9 \ + --hash=sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4 \ + --hash=sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf \ + --hash=sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4 \ + --hash=sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca \ + --hash=sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76 \ + --hash=sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d \ + --hash=sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af \ + --hash=sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911 \ + --hash=sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a \ + --hash=sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9 \ + --hash=sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278 \ + --hash=sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6 \ + --hash=sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f \ + --hash=sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e \ + --hash=sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da +runs==1.3.0 \ + --hash=sha256:cca304b631dbefec598c7bfbcfb50d6feace6d3a968734b67fd42d3c728f5a05 \ + --hash=sha256:e71a551cfa8da9ef882cac1d5a108bda78c9edee5b8d87e37c1003da5b6a7bed +setproctitle==1.3.7 \ + --hash=sha256:047138279f9463f06b858e579cc79580fbf7a04554d24e6bddf8fe5dddbe3d4c \ + --hash=sha256:0564a936ea687cd24dffcea35903e2a20962aa6ac20e61dd3a207652401492dd \ + --hash=sha256:0b5074649797fd07c72ca1f6bff0406f4a42e1194faac03ecaab765ce605866f \ + --hash=sha256:10e92915c4b3086b1586933a36faf4f92f903c5554f3c34102d18c7d3f5378e9 \ + --hash=sha256:1607b963e7b53e24ec8a2cb4e0ab3ae591d7c6bf0a160feef0551da63452b37f \ + --hash=sha256:23e520776c445478a67ee71b2a3c1ffdafbe1f9f677239e03d7e2cc635954e18 \ + --hash=sha256:2906b6c7959cdb75f46159bf0acd8cc9906cf1361c9e1ded0d065fe8f9039629 \ + --hash=sha256:2dc99aec591ab6126e636b11035a70991bc1ab7a261da428491a40b84376654e \ + --hash=sha256:316664d8b24a5c91ee244460bdaf7a74a707adaa9e14fbe0dc0a53168bb9aba1 \ + --hash=sha256:3384e682b158d569e85a51cfbde2afd1ab57ecf93ea6651fe198d0ba451196ee \ + --hash=sha256:3a57b9a00de8cae7e2a1f7b9f0c2ac7b69372159e16a7708aa2f38f9e5cc987a \ + --hash=sha256:4a5e212bf438a4dbeece763f4962ad472c6008ff6702e230b4f16a037e2f6f29 \ + --hash=sha256:5fa1953126a3b9bd47049d58c51b9dac72e78ed120459bd3aceb1bacee72357c \ + --hash=sha256:61e96febced3f61b766115381d97a21a6265a0f29188a791f6df7ed777aef698 \ + --hash=sha256:6915964a6dda07920a1159321dcd6d94fc7fc526f815ca08a8063aeca3c204f1 \ + --hash=sha256:7081f193dab22df2c36f9fc6d113f3793f83c27891af8fe30c64d89d9a37e152 \ + --hash=sha256:7f47accafac7fe6535ba8ba9efd59df9d84a6214565108d0ebb1199119c9cbbd \ + --hash=sha256:80e177eff2d1ec172188d0d7fd9694f8e43d3aab76a6f5f929bee7bf7894e98b \ + --hash=sha256:97a090fed480471bb175689859532709e28c085087e344bca45cf318034f70c4 \ + --hash=sha256:9888ceb4faea3116cf02a920ff00bfbc8cc899743e4b4ac914b03625bdc3c300 \ + --hash=sha256:9cc9b901ce129350637426a89cfd650066a4adc6899e47822e2478a74023ff7c \ + --hash=sha256:a20fb1a3974e2dab857870cf874b325b8705605cb7e7e8bcbb915bca896f52a9 \ + --hash=sha256:a5d1cb3f81531f0eb40e13246b679a1bdb58762b170303463cb06ecc296f26d0 \ + --hash=sha256:a600eeb4145fb0ee6c287cb82a2884bd4ec5bbb076921e287039dcc7b7cc6dd0 \ + --hash=sha256:a7d159e7345f343b44330cbba9194169b8590cb13dae940da47aa36a72aa9929 \ + --hash=sha256:acb9097213a8dd3410ed9f0dc147840e45ca9797785272928d4be3f0e69e3be4 \ + --hash=sha256:b0304f905efc845829ac2bc791ddebb976db2885f6171f4a3de678d7ee3f7c9f \ + --hash=sha256:b08b61976ffa548bd5349ce54404bf6b2d51bd74d4f1b241ed1b0f25bce09c3a \ + --hash=sha256:b1cac6a4b0252b8811d60b6d8d0f157c0fdfed379ac89c25a914e6346cf355a1 \ + --hash=sha256:b74774ca471c86c09b9d5037c8451fff06bb82cd320d26ae5a01c758088c0d5d \ + --hash=sha256:b7cb05bd446687ff816a3aaaf831047fc4c364feff7ada94a66024f1367b448c \ + --hash=sha256:bc2bc917691c1537d5b9bca1468437176809c7e11e5694ca79a9ca12345dcb9e \ + --hash=sha256:c18246d88e227a5b16248687514f95642505000442165f4b7db354d39d0e4c29 \ + --hash=sha256:c3736b2a423146b5e62230502e47e08e68282ff3b69bcfe08a322bee73407922 \ + --hash=sha256:cdd8aa571b7aa39840fdbea620e308a19691ff595c3a10231e9ee830339dd798 \ + --hash=sha256:cf2727b733e90b4f874bac53e3092aa0413fe1ea6d4f153f01207e6ce65034d9 \ + --hash=sha256:cff72899861c765bd4021d1ff1c68d60edc129711a2fdba77f9cb69ef726a8b6 \ + --hash=sha256:d8828b356114f6b308b04afe398ed93803d7fca4a955dd3abe84430e28d33739 \ + --hash=sha256:db0fd964fbd3a9f8999b502f65bd2e20883fdb5b1fae3a424e66db9a793ed307 \ + --hash=sha256:db116850fcf7cca19492030f8d3b4b6e231278e8fe097a043957d22ce1bdf3ee \ + --hash=sha256:de879e9c2eab637f34b1a14c4da1e030c12658cdc69ee1b3e5be81b380163ce5 \ + --hash=sha256:f1704c9e041f2b1dc38f5be4552e141e1432fba3dd52c72eeffd5bc2db04dc65 \ + --hash=sha256:f8d961bba676e07d77665204f36cffaa260f526e7b32d07ab3df6a2c1dfb44ba \ + --hash=sha256:fe5ca35aeec6dc50cabab9bf2d12fbc9067eede7ff4fe92b8f5b99d92e21263f +shellingham==1.5.4 \ + --hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \ + --hash=sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de +sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc +sqlalchemy==2.0.51 \ + --hash=sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5 \ + --hash=sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d \ + --hash=sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba \ + --hash=sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f \ + --hash=sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9 \ + --hash=sha256:2cf39aabdf48e87c1c2c2ed6d20d33ffa0733b3071ce9c5f66357947dd009080 \ + --hash=sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd \ + --hash=sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195 \ + --hash=sha256:4a011ea4510683319ce4ed274b56ee05194b39b6da9d09ca7a39388f0fa84dcc \ + --hash=sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e \ + --hash=sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400 \ + --hash=sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07 \ + --hash=sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a \ + --hash=sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9 \ + --hash=sha256:9f380393be5abeb6815f68fd39271b95127173511b6706b0a630a9995d53f8f5 \ + --hash=sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604 \ + --hash=sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265 \ + --hash=sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d \ + --hash=sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b \ + --hash=sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5 \ + --hash=sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389 \ + --hash=sha256:ca8435d13829b92f4a97362d91975154a4015db3a2634154e1754e9a915e6b86 \ + --hash=sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260 +sse-starlette==3.4.5 \ + --hash=sha256:83072538bc211a2f68b7b0422226c4af3e9b62e106e07034664b832ca019842a \ + --hash=sha256:e71bad53323f65573c3864a6c3bd0c1eb6e5f092b2e48082b0c35927d19ca296 +starlette==1.3.1 \ + --hash=sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0 \ + --hash=sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6 +starlette-compress==1.7.1 \ + --hash=sha256:cd229d64f93789f90137bc08391ca946639812f514c9f5db72ef232687753cea \ + --hash=sha256:f4df7aa6b0029ec5c4ae960040cd5d375563a4d3f7fc134bd108ebc0ed61536c +tenacity==9.1.4 \ + --hash=sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55 \ + --hash=sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a +tiktoken==0.13.0 \ + --hash=sha256:059c8ecf554eb5b41e6e054ba467b871b03277d267dee7244380aca4359747d4 \ + --hash=sha256:125bc05005e747f993a83dc67934249932d6e4209854452cd4c0b1d53fba3ba2 \ + --hash=sha256:165cf1820ea4a354985c2490a5205d4cc74661c934aca79dd0368232fff94e0f \ + --hash=sha256:2c397ddda233208345b01bd30f2fca79ff730e55731d0108a603f9bc57f6af3b \ + --hash=sha256:303f7d91b4fce3baddbcde05c139091d4caa5026ac7214c1dc7ff7a71ee429ff \ + --hash=sha256:32ac870a806cfb260a02d0cb70426aef02e038297f8ad50df5040bb5af360791 \ + --hash=sha256:36217497eaffc158607a3b26f065300db2aefd43b115263f3b9688ce38146173 \ + --hash=sha256:3f277ebea5edd7b8bf03c6f9431e1d67d517530115572b2dc1d465326e8f88c7 \ + --hash=sha256:43cee3e5400573b2046fbf092cc7a5bc30164f9e4c95ce20714da929df48737a \ + --hash=sha256:4d9980f11429ed2d737c463bb1fb78cf330caa026adf002f714aced7849a687b \ + --hash=sha256:51384448aa508e4df84c0f7c1dc3211c7f7b8096325660ee5fc82f3e11b381ce \ + --hash=sha256:5d48843bee149630eb735a99e1f4a85b47308d21868ea63163f6e87768d3cfed \ + --hash=sha256:5df5d1507bd245f1ccad4a074698240021239e455eb0bb4ced4e3d7181872154 \ + --hash=sha256:5e6358911cab4adee6712da27d65573496a4f68cf8a2b5fca6a4ad10fc5748cf \ + --hash=sha256:6b1615f0ff71953d19729ceb18865429c185b0a23c5353f1bbca34a394bf60f7 \ + --hash=sha256:6c43a675ca14f6f2749ba7f12075d37456015a24b859f2517b9beb4ef30807ec \ + --hash=sha256:6eb4a5bfbc6426938026b1a334e898ac53541360d62d8c689870160cc80abd67 \ + --hash=sha256:75ab9bc99fa020a4c283424590ecd7f3afd70c1c281cb3fa3192a6c3af9f9615 \ + --hash=sha256:7bfe1849caa65d1e1d9871817170ec497bbb7984e182012e1bdce72f66608cdb \ + --hash=sha256:7de52e3f566d19b3b11bd37eea552c6c305ad74081f736882bd44d148ed4c48d \ + --hash=sha256:8f2d16e7a7c783ad81f36e457d046d1f1c8af70b22aec8a13238efe531977c41 \ + --hash=sha256:8fe806a50664e83a6ffd56cbd1e4f5dcc6cd32a3e7538f70dc38b1a271384545 \ + --hash=sha256:91c180fe255bd5a86d8316210d2833a1d4d33d026cd86a67812f4773743c8d26 \ + --hash=sha256:95097e4f89b06403976e498abf61a0ee73a7497e73fb599cb211d8197a054d91 \ + --hash=sha256:975cbd78d085d75d26b59660e262736dcaed1e35f8f142cd6291025c01d25486 \ + --hash=sha256:a116178fa7e1b4065bff05214360373a65cac22f965be7b3f73d00a0dbfe7649 \ + --hash=sha256:c9435714c3a84c2319499de9a300c0e604449dd0799ff246458b3bb6a7f433c1 \ + --hash=sha256:e28157350f7ebf35008dd8e9e0fdb621f976e4230c881099c85e8cf07eaa50e2 \ + --hash=sha256:fc1c44cd37b43fc46bae593129164f4f281e82ea116b57a85aa81bda57eafc94 +tqdm==4.68.3 \ + --hash=sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482 \ + --hash=sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03 +typer==0.26.8 \ + --hash=sha256:3512ca79ac5c11113414b36e80281b872884477722440691c89d1112e321a49c \ + --hash=sha256:c244a6bd558886fe3f8780efb6bdd28bb9aff005a94eedebaa5cb32926fe2f7e +typing-extensions==4.16.0 \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \ + --hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5 +typing-inspection==0.4.2 \ + --hash=sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 \ + --hash=sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464 +tzdata==2026.2 ; sys_platform == 'win32' \ + --hash=sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10 \ + --hash=sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7 +urllib3==2.8.0 \ + --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \ + --hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63 +uuid-utils==0.16.2 \ + --hash=sha256:01f81c71cf2185de0707e9d2f248e17025ba50af0acd3cbf51cd8aea96c2e0be \ + --hash=sha256:0529b1ef0788f663e1211d221b59a38ec67f9b084f1ea5342ba84358b3d87e98 \ + --hash=sha256:1a30b6a5790acb854e4b65fae7875e5d3c6f8076fa9c91dac43ff9e28380bc52 \ + --hash=sha256:27271b37fbc6812bb1542c4b8e22ee00223a6bf7f62b1f38d3bcf8e92f6d9acd \ + --hash=sha256:280d4f1f22dd2e79c1cc31ffc7fc26dc3534ffc114dedcdd29cc8489c5ce9c98 \ + --hash=sha256:2e9ca7f5e215373cc9c147172170a0b1a4ab0dee9cc62fe446d9b075f31e3241 \ + --hash=sha256:4125bf6ed3ae443c05e140f8585d174b9d647295b12034d5ec94ae2ae38edefa \ + --hash=sha256:43cc72a92694d08ade8faadacf928857d9cceb84b449473246ae4e4f263d7d22 \ + --hash=sha256:4942b26ad12c5187bac52b7fb4685040139ff0df9a19cde33e5025326f6180fc \ + --hash=sha256:503f020acc7dbeb39c47fa33cf2971cf5960fa11f8394513fac461762a90c556 \ + --hash=sha256:5119bec75f56bd028d97472f72b1ed723a0d60b09a48017dc70a3cb1892ed081 \ + --hash=sha256:511b5fde12d29c37a9badd399af62105bb2f4696aa10eb18be74e7b9ca84413a \ + --hash=sha256:585d3adf73afa60348bf2bd529491c640a692350e76d8ff3974455e273aadfe7 \ + --hash=sha256:5dfc3e9e75139a84898771d31958ece6cdee8e8f127700aa8aa26a4f1a348d57 \ + --hash=sha256:61454f2139424a6cff14eca7849c28b3350f261453b74075aa20fe99592dbb16 \ + --hash=sha256:6f064dc54c6abecb09eb104d953bfb079f3c395e0d6b18899979f852d1083549 \ + --hash=sha256:71192a59d473f3f638e2a238905046e2942006ad90ac5ec10d578e58ff9a08ce \ + --hash=sha256:725110434a1d482a639a9ac467a24f1cb531d84ab52e454a13fe145b10b42cae \ + --hash=sha256:72cfd9ff1e8a7c371a044687e77eb873721c4a9f4814e453439bfba595b84303 \ + --hash=sha256:79c5a3bd4301257b9a524efd16baf61ea65cd0d1b60b47d80f20b151fd65a09f \ + --hash=sha256:8197870739a3094990743a80f075fa0b17beafd6c187e5f360e021d90a12a6d1 \ + --hash=sha256:8323136bb02355c1b973492ab98b0722206dfdedfb148e4115c35fcdf3889bad \ + --hash=sha256:840b21e609a9b203eee06bdc73e18397154447a9814a8e78d9b68e5104d9802f \ + --hash=sha256:8b8e325e61f918caf74ca540e3384b81e6e22aea782e57f615d15fc9773b96c8 \ + --hash=sha256:90903ab7fcdfb0300390c15f5a68cb91f15139d9a1a93f134c783d7a973fa269 \ + --hash=sha256:909e26fa2451c8db31b9ed1d3c8e4ecf513b6d1619db4205997fe99eb6b4ef4f \ + --hash=sha256:9282677ebf2ea5b437c20d16e75bcd7629bdc205018f95557b33b76868d8bb5b \ + --hash=sha256:97ee6f5e803ea571f5f6da42efc97d8c5a13f121043680177f8470529b94e855 \ + --hash=sha256:9b4520521aa46a2582fe1829c535fe60b78999b89257db998df3816eb895bdf3 \ + --hash=sha256:9bf8bfdffb22f620635580b17fd178272f30a9841b824b19b935c8db64bf09b6 \ + --hash=sha256:9e97ab941660f781a8e45f15aba9ee01b40dbb96adb5c43617c1671a4604b25b \ + --hash=sha256:9fe600ab7d3d4eb56986e814042c917e728ac92cd8a41f099a6b59b84d8bf9e6 \ + --hash=sha256:aab7cdf28a3e2859ca4f40a3e3bf53eb35895039c80d4d8d8c5e15b90346c55c \ + --hash=sha256:ae5fa2007fd26d26f7b09e76259d5ca99bec191616207ca929f8dca12da08129 \ + --hash=sha256:c19b7d595d12923da682ed13d313c2333b9ebf214e65a47a24927a8a3a81b191 \ + --hash=sha256:c1dbe65ce6d46c5f645356d64bfb2de7564e2426ca8c9b1a0a401d6f7ae5cc22 \ + --hash=sha256:c3bf41b696b0fe808df1b4091c70273a52ea033b0fe97341cd67ecd76d22bb3a \ + --hash=sha256:cae08df8695f4b01fce2a8ab50e9e310971276d85dfc7103e977bed52d365094 \ + --hash=sha256:cc25ad320c9b44c2d3ed33aff4f85b0b277bef4ff79b12c01ee58b52ea44be1d \ + --hash=sha256:d0ca752d51d1004caff65fccffd44b32a26cb099b546e0512cfa09facb683d6c \ + --hash=sha256:dc4b9d96a2c689d664cf3fc7f7db46b82d2821fb2ce8a4f0798fc0a92c1569f8 \ + --hash=sha256:dcdfcab60562d12dd43c1a6f495b1d089e41f0e10fac37d94db285d72b678c23 \ + --hash=sha256:dd7aa18db5cc826d482d876a826fee445839701f81f78567e7c74b4458d57a84 \ + --hash=sha256:e10a02b3a31ed44c7c9a96abde335f5fa222735e73f3081d693414377eb3b016 \ + --hash=sha256:e44020a4532229ccfbba353138539774686350dda71cf4368e257973dd8ba403 \ + --hash=sha256:ea175649789f1e93edbf1a0440cab18c9838977703917221777691d8d988d7bf \ + --hash=sha256:f69658c42411540cf58be958a47e317fd2302cc0b613ea5cff1e60d87be2846d \ + --hash=sha256:f7a44f8250ec178c0af703c3f1b6e81865a771272ae735ca403f27c95c62f132 \ + --hash=sha256:fa637e4f314ad5b59ff6d8e809d506443d68bef30bfaecdfcfe02cce689abb2f \ + --hash=sha256:fadd23eee409237fb8637a35796a6e108873c28b40f7de89a36685f18ca055ad \ + --hash=sha256:fcc329be41bb6534ecb03e50596179ab76c7643ced33d13c66967d5ae1869663 \ + --hash=sha256:fd32dbca0792b9683160151dc07fad11b915020eed7c82b43faf0862c2ff06a0 +uvicorn==0.49.0 \ + --hash=sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f \ + --hash=sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3 +uvloop==0.22.1 ; (platform_python_implementation == 'CPython' and sys_platform == 'cygwin') or (platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32') \ + --hash=sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e \ + --hash=sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8 \ + --hash=sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad \ + --hash=sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77 \ + --hash=sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733 \ + --hash=sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0 \ + --hash=sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6 \ + --hash=sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473 \ + --hash=sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21 \ + --hash=sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705 \ + --hash=sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702 \ + --hash=sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f \ + --hash=sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d \ + --hash=sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370 \ + --hash=sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4 \ + --hash=sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e \ + --hash=sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9 \ + --hash=sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2 \ + --hash=sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42 +watchfiles==1.2.0 \ + --hash=sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9 \ + --hash=sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98 \ + --hash=sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db \ + --hash=sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f \ + --hash=sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5 \ + --hash=sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427 \ + --hash=sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4 \ + --hash=sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa \ + --hash=sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c \ + --hash=sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906 \ + --hash=sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c \ + --hash=sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c \ + --hash=sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077 \ + --hash=sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01 \ + --hash=sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9 \ + --hash=sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2 \ + --hash=sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658 \ + --hash=sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5 \ + --hash=sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0 \ + --hash=sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7 \ + --hash=sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5 \ + --hash=sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201 \ + --hash=sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8 \ + --hash=sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1 \ + --hash=sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898 \ + --hash=sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44 \ + --hash=sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5 \ + --hash=sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a \ + --hash=sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc \ + --hash=sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5 \ + --hash=sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8 \ + --hash=sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9 \ + --hash=sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0 \ + --hash=sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e \ + --hash=sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0 \ + --hash=sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7 \ + --hash=sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55 \ + --hash=sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb \ + --hash=sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0 \ + --hash=sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26 \ + --hash=sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3 \ + --hash=sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838 \ + --hash=sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71 \ + --hash=sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717 \ + --hash=sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d \ + --hash=sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44 \ + --hash=sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2 \ + --hash=sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b \ + --hash=sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22 \ + --hash=sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6 \ + --hash=sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165 \ + --hash=sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5 \ + --hash=sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799 \ + --hash=sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379 \ + --hash=sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925 \ + --hash=sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72 \ + --hash=sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08 \ + --hash=sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4 +wcwidth==0.8.2 \ + --hash=sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda \ + --hash=sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85 +websockets==16.0 \ + --hash=sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c \ + --hash=sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe \ + --hash=sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec \ + --hash=sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64 \ + --hash=sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8 \ + --hash=sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156 \ + --hash=sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d \ + --hash=sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad \ + --hash=sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2 \ + --hash=sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03 \ + --hash=sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230 \ + --hash=sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8 \ + --hash=sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea \ + --hash=sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6 \ + --hash=sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5 \ + --hash=sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f \ + --hash=sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00 \ + --hash=sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b \ + --hash=sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39 \ + --hash=sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9 \ + --hash=sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79 \ + --hash=sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac \ + --hash=sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5 \ + --hash=sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c \ + --hash=sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1 \ + --hash=sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767 \ + --hash=sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a \ + --hash=sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d \ + --hash=sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82 \ + --hash=sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5 \ + --hash=sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f \ + --hash=sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c \ + --hash=sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89 \ + --hash=sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da +xmod==1.10.0 \ + --hash=sha256:b40b2a54d56684b01eb9627892b0c179918e8ef0bd4d7f3bac7a3fdba11cd6e6 \ + --hash=sha256:bebf8493b7ac63097401590a329c9ed20da224de0583a522e7ccb634af122f5a +xxhash==3.8.0 \ + --hash=sha256:0643b7d9f598f6da6f1f6b899f4358250d0fb853242e2d712cbde27bf5a99d29 \ + --hash=sha256:07dd44d992ebd456752bc25b1c42cd172d94bd8cb24049300449ad0716081c3a \ + --hash=sha256:084312171a9798dea85e924b2674f5e1a44933050a1ea1cb1c6b1364e004c66c \ + --hash=sha256:08c34553cd7ceb3bfcfca344dc70305a45430429b5d58a67750f2a58364f638f \ + --hash=sha256:0b0836dee6022e22ba516ebfa8f76c6e4bda08d6c166c553e40867bac89e4a54 \ + --hash=sha256:0cd25bbbab37d898f6e5a90905ce6ae2c1f8bd6668c07cef406fb3e8c8c570dd \ + --hash=sha256:0dbaa73df10414ea1e41b98691a9d8241d4c47ad8d02c726587a3cda05278e53 \ + --hash=sha256:14ebc1559e8a9a481d0d5506b87678942fcdfa794d4aa55cdd2a0fb175d4245a \ + --hash=sha256:196fc132683d9311a0bdce8388ee52bfa07fdc1987cc428a27956e47ccd7b50d \ + --hash=sha256:1a54ad5a2a96cdf1ee7a935d38bc63daa6095530095a916f644f1ab76604ced5 \ + --hash=sha256:1da00075f1605794298878cb587f7533329693e2a0c45bbd25d6353644add675 \ + --hash=sha256:1ec9afdd53ac5f4fd1d8918807ba6c35ba62269086af794884b9f168a73331ea \ + --hash=sha256:208e6a8b93426896d803224e9fabe26f8b9c651e8381a80b1fa31812faa091e3 \ + --hash=sha256:21f6797afdc7abb0ffae059a0d1619c84a5368115bc0abd48f9803ab56a5d35e \ + --hash=sha256:22c0b17da2f9fea0f8836538512249871b359141616bad44c58d238b5f011f40 \ + --hash=sha256:281897e5c516769694c999f5c50fd1e9acb27acbff187282a8ac77c38b6a9be5 \ + --hash=sha256:2b77c301b644cd9b4d0749a3291081ec2048a6bef7fe0487c993bbba3efb9ce0 \ + --hash=sha256:2fd4b60e8d9fc3923f39079f185b3425e6d76636fcb66d82a33dd7eba7c30f2f \ + --hash=sha256:3118600a3102d4707dc1c485dbc3acbbbf37819069ad3e7854e77b923745d76b \ + --hash=sha256:31904979198e913239cb61b49f5b849696aeb3b03340da815d1491ec74dcc602 \ + --hash=sha256:353953ea18f5c3fbdd13936fb536aacfb47d5bc06eef0919b1a355df61f7cc31 \ + --hash=sha256:35c5d843bb7ac1dfdb125ef4181fe4c2e01c2275856e6b699de89e9eb5c69c8d \ + --hash=sha256:36434c1d1b0a4729df1fa26ab11bffed1ba52666c0beb605c98a995b470cd143 \ + --hash=sha256:37c9943e18f569f76a8b7d5d01bfe0716f7762c396096ceb42a47eb3d5ecf641 \ + --hash=sha256:38b0cb0ab7f283413b7cace2bf710d7cf8f702ea82cbc683908691d52028a89b \ + --hash=sha256:3afa1422a32c7c8e79ad5121dc21eaa5cee9e9e67bffca3f15d15d220d371908 \ + --hash=sha256:3bc2a09b98b8f85c75208cd2b2d2aecf40c77ecb2d72f6bf9757db51a98d3499 \ + --hash=sha256:3d49465646b1a5e3b1729c5f636e05676a2fb52e203e3b22a5411c416c4c5302 \ + --hash=sha256:3e30e5c057f483c3c53a11b53eba091a737cb19dfead36c8b23bf5beb4a169cd \ + --hash=sha256:41a30a1d0ba978238742a374875c15979e0faed0a65294f3ff4d9410057ee8b6 \ + --hash=sha256:43705f917b8b817d6994851bf3725b98b4c95e64186404d9a6dbc1acf12fd140 \ + --hash=sha256:43fc9aaba10ab4267c90793601f60d35c3c9caa1544eceb483618a71ad9ce7da \ + --hash=sha256:4931ea93840f750a908efebaf23c71004feacc1a4649ef601b96d400a505c9a9 \ + --hash=sha256:4bbacf2e938526969f8ab3334d4ac3da14ea059e1dfd1339a92f9091467e750f \ + --hash=sha256:512eb937c9457e6057e230e005c4709dd2ab63a5989f854d69f31db905750a62 \ + --hash=sha256:54e80e803cb34c8a1d278b491e543af40a588d288589c3e6becc991d5328b46b \ + --hash=sha256:551fda694938be910529452a89175137c58b4739e41fadff3c047e24b1d74a3b \ + --hash=sha256:55ce59f9af37ac861947b43ea3ce7b294b5de77a1234b558d0f07ffad0197624 \ + --hash=sha256:5875d99d3540367d43779551dd22c813420b84a103e418d791095b9808fdca57 \ + --hash=sha256:591d5eb256abf59438800ace2730ac33f77bc6ab8c3623fab1ea24d9d8b28f3a \ + --hash=sha256:5b00b82f1be708da9404fefd658cf5cf3be5ee3be2aae4bfe3b874255badd342 \ + --hash=sha256:5e4208fb85c950ddf7118b040bca15179c3bf9b7eb8bebe5e6ef067fc8af16a7 \ + --hash=sha256:5e521368ed79ae6c4d31e1e417726643c49d7d6e286f4fdabf9a8330ed8a8ff7 \ + --hash=sha256:5e7a3e3bbe3a56bff70acc9b72576670e793b0184de3d1b9cda2bf697d17f630 \ + --hash=sha256:68594a54be2eb5992d9b0d0a0ec7c32a7a8e930f06d6cb951d69708055680994 \ + --hash=sha256:6a0127688d116ec0c225e7e1f744e3f206de2b8822ffeb31a9ab5cc6384f92c5 \ + --hash=sha256:6a1a9e845bd3bbc57d9356819e0d198fe23282e0576b398a6282a0f8fdc75aef \ + --hash=sha256:6d1d6179e26830c6690fac63f76d372f69714b977e12ca9c42188a60f51c59f5 \ + --hash=sha256:715c611582004e75010517b919776c5dbc00aae03054dc9fd72484a23fd1862f \ + --hash=sha256:7338ad13f2b273a1ef0ea97b2db0a059fdb3a1a29298bfa145937c0e4152d341 \ + --hash=sha256:7c92427a56a12f4d5c7bb26dbb9e9a4658c313ecb6c2f1dca349902e3822df07 \ + --hash=sha256:7d6dbb976d6e3b3be51bad16b13de7f4980e6aebd0aa51c5a14dfcc0fedd495e \ + --hash=sha256:7ed37b0c95d8fb3fbaad5e13cc0a9727eb8739d1d54b2adef28108c250cada3a \ + --hash=sha256:7f4eecf800275e62b6bcb41e65f361f2277cc886c2bff4e299959d701e5fcf93 \ + --hash=sha256:82f0102a2a3760287b7cd7f9e0a30edd4c3b18762ed1a242208d43c8e2bcf30b \ + --hash=sha256:842d147983110e5a4f533f98f4f5bc851a08c7ca00aaa30649e8d5f9a6d4e47a \ + --hash=sha256:8fba3d08c246201a1a0a6cece53a0b3b0890fc16adbe1edb245fcfcbf4eb0ce2 \ + --hash=sha256:97d7bd715ea5050b6c9638b52c62adf3055b648ef6eee6892a4cd9697b530191 \ + --hash=sha256:9a8d08707b4100ebce598fc59fadf04b42d79b855818d6994f8f0fffd1df8edb \ + --hash=sha256:9c71e3755a8320d29c351126d550930349be22b44bac1a559caf12ab78b53e9f \ + --hash=sha256:9f17e09b035f2a0139536da53deb392b62ee259dc2a2189be12b06a7dd50489b \ + --hash=sha256:9fc8453642c1c6d38b4fbac8901c2452ce1fa88b27f003bfee6703cbfae9bd63 \ + --hash=sha256:9ffbde09743ebaf8957b8426948fbe85eab5e5de0d29eec407fcff5a2812a3cc \ + --hash=sha256:a2c94f5a9a775f36cc522fa2a7e8e2cec512e252d2ac056759f753dc68a79ffc \ + --hash=sha256:a5e6497cefcb2d67f1745c66df9718a99112583af6cc2b70da0312a2eb939f1e \ + --hash=sha256:a6dee3952c2b6e82e7f1dbc5dbc6167f9c84126851def7926e32827c2816169c \ + --hash=sha256:aafc3eab99c50508852e34307e9565933bf128cad084cac7d2471b7ab1743de0 \ + --hash=sha256:b21db84df7b9d54d9e4195a964243c1b32d745c6fbc0cfcfffee1d4bd297196a \ + --hash=sha256:b32e50dd85f0b67b2b95eb59cd3242052f6b27b70e9e73b27629686c592e3ea3 \ + --hash=sha256:b8414a66a7524596d841cad5dc1adab6ce76848db5ab2b83db911fbdab1417af \ + --hash=sha256:ba14843f20df2dce6ff6684411a56ae53da44336546c55f8947e70aebb8cdd21 \ + --hash=sha256:ba73801c87d44fa37b2a5feab3004f0a654506027bf032ceb154d94bb74ea772 \ + --hash=sha256:bb043da412e478e7b1db3407051124b85b133803794d3809ad6d92870b304fc7 \ + --hash=sha256:bf8ff8e12416c9fa05b43c7509b9332d6ffc4090413c4e7a1dee8599763b6d59 \ + --hash=sha256:c2853dea1e30ed00ca87dd87d76da5da063d302b823b3fb80ccd18421de0f251 \ + --hash=sha256:cebbb322df4d97d8ef2704f49ed2f6f21f6702fafa0dc0c2a6ae70e904205689 \ + --hash=sha256:cf5427602dda15d8ce3c6d870d29bf07d43975f59c9d6d3f7f6f93a901b28b12 \ + --hash=sha256:d1e0dbc510cff94c5efbcc2b82c28b41519fad09b5b1f9f3d99c63e3940e49a0 \ + --hash=sha256:d72b2204f37840b0f16f34192c09b994b97bd25823d723d47a1eddfacf06eb43 \ + --hash=sha256:d761f983a315630eff18c2fec7360c6b6946f82748026e779336eb8141ef3eba \ + --hash=sha256:d7ece11a132325353890a144c30119073617a1299c593ca29b96c315b07e1edd \ + --hash=sha256:dfb5411af3b77c75e99db100aa15c5ba623c85d72c565e4d7a0ed1a986ff766e \ + --hash=sha256:ec5eb3d28fbb9802c6d2526f772133a06c91d6f03756fcc67c834b642ffdd51d \ + --hash=sha256:ec6666a5311beae3f6cb5f2fd28c2b77e2df32702c8206f45c786a6ef81b3751 \ + --hash=sha256:efcacb644a915f010dc477447b045e5dcde1afaa40d16b2f0f8e7cd99c9e1635 \ + --hash=sha256:f3786a9beb9a3b76241cb7db5f5388b460682c12204236389e3221963fc626a6 \ + --hash=sha256:fc4bd14f873cd0b420f6f1ff5b5cd0dbfeb05b044a11bb9345bcbbf9749636e3 \ + --hash=sha256:ff19d016a41c90d1f519005887191896b6da1274e1d5d48b347e17eb798ffc5a +zstandard==0.25.0 \ + --hash=sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64 \ + --hash=sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a \ + --hash=sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6 \ + --hash=sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431 \ + --hash=sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250 \ + --hash=sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f \ + --hash=sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851 \ + --hash=sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3 \ + --hash=sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9 \ + --hash=sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6 \ + --hash=sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5 \ + --hash=sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137 \ + --hash=sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa \ + --hash=sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd \ + --hash=sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611 \ + --hash=sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b \ + --hash=sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e \ + --hash=sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa \ + --hash=sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf \ + --hash=sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902 \ + --hash=sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc \ + --hash=sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98 \ + --hash=sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a \ + --hash=sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097 \ + --hash=sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea \ + --hash=sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09 \ + --hash=sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb \ + --hash=sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7 \ + --hash=sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b \ + --hash=sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b \ + --hash=sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91 \ + --hash=sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049 \ + --hash=sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a \ + --hash=sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00 \ + --hash=sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072 \ + --hash=sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c \ + --hash=sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065 \ + --hash=sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512 \ + --hash=sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1 \ + --hash=sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f \ + --hash=sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b \ + --hash=sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea \ + --hash=sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277 \ + --hash=sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778 \ + --hash=sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2 \ + --hash=sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f \ + --hash=sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94 \ + --hash=sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708 \ + --hash=sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313 \ + --hash=sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4 \ + --hash=sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551 \ + --hash=sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01 diff --git a/infrastructure/images/contextforge/os-bases/Dockerfile.builder b/infrastructure/images/contextforge/os-bases/Dockerfile.builder new file mode 100644 index 0000000..f48b608 --- /dev/null +++ b/infrastructure/images/contextforge/os-bases/Dockerfile.builder @@ -0,0 +1,25 @@ +# syntax=docker/dockerfile:1.12 +# Scratch-derived, linux/amd64 only. Freeze by registry manifest digest after validation. +FROM registry.access.redhat.com/ubi10@sha256:1e32d829a4de713a835d702d3bc05f0c448a08590796281b7dec181912e9aeb8 +LABEL io.itworx.contextforge.os-base.role="builder" \ + io.itworx.contextforge.os-base.upstream-commit="1e58a0434a78bbdbe1e975abeb0bed1043ff14c1" \ + io.itworx.contextforge.os-base.upstream-containerfile-sha256="2ab0187f3a63eced093a1dec3141ec3e30ef879eb7de0dfd9e4bc5fce33f64f2" \ + io.itworx.contextforge.os-base.parent-ref="registry.access.redhat.com/ubi10@sha256:1e32d829a4de713a835d702d3bc05f0c448a08590796281b7dec181912e9aeb8" \ + io.itworx.contextforge.os-base.platform="linux/amd64" +SHELL ["/bin/bash", "-euo", "pipefail", "-c"] + +RUN set -euo pipefail \ + && dnf upgrade -y \ + && dnf install -y --allowerasing \ + python3.12 \ + python3.12-devel \ + binutils openssl-devel gcc postgresql-devel gcc-c++ curl libpq-devel \ + && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1 \ + && dnf clean all + +RUN set -eu \ + && mkdir -p /usr/share/itworx \ + && rpm -qa --qf '%{NAME}|%{EPOCHNUM}|%{VERSION}|%{RELEASE}|%{ARCH}\n' > /tmp/itworx-rpm-inventory.unsorted \ + && LC_ALL=C sort /tmp/itworx-rpm-inventory.unsorted > /usr/share/itworx/rpm-inventory.txt \ + && rm /tmp/itworx-rpm-inventory.unsorted \ + && chmod 0444 /usr/share/itworx/rpm-inventory.txt diff --git a/infrastructure/images/contextforge/os-bases/Dockerfile.runtime b/infrastructure/images/contextforge/os-bases/Dockerfile.runtime new file mode 100644 index 0000000..ab1c17a --- /dev/null +++ b/infrastructure/images/contextforge/os-bases/Dockerfile.runtime @@ -0,0 +1,28 @@ +# syntax=docker/dockerfile:1.12 +# Scratch-derived, linux/amd64 only. Freeze by registry manifest digest after validation. +FROM registry.access.redhat.com/ubi10/ubi-minimal@sha256:afbfb5c18c628796fed3ad3122dcf096e0b2dec9dad3fbabe8620de06bc6f078 +LABEL io.itworx.contextforge.os-base.role="runtime" \ + io.itworx.contextforge.os-base.upstream-commit="1e58a0434a78bbdbe1e975abeb0bed1043ff14c1" \ + io.itworx.contextforge.os-base.upstream-containerfile-sha256="2ab0187f3a63eced093a1dec3141ec3e30ef879eb7de0dfd9e4bc5fce33f64f2" \ + io.itworx.contextforge.os-base.parent-ref="registry.access.redhat.com/ubi10/ubi-minimal@sha256:afbfb5c18c628796fed3ad3122dcf096e0b2dec9dad3fbabe8620de06bc6f078" \ + io.itworx.contextforge.os-base.platform="linux/amd64" + +RUN microdnf upgrade -y --nodocs --setopt=install_weak_deps=0 \ + && microdnf install -y --nodocs --setopt=install_weak_deps=0 \ + python3.12 \ + ca-certificates \ + procps-ng \ + shadow-utils \ + libpq \ + && microdnf clean all \ + && rm -rf /var/cache/yum \ + && ln -sf /usr/bin/python3.12 /usr/bin/python3 \ + && groupadd --gid 10001 app \ + && useradd --uid 10001 --gid 10001 --home-dir /app --shell /sbin/nologin --no-create-home --comment app app + +RUN set -eu \ + && mkdir -p /usr/share/itworx \ + && rpm -qa --qf '%{NAME}|%{EPOCHNUM}|%{VERSION}|%{RELEASE}|%{ARCH}\n' > /tmp/itworx-rpm-inventory.unsorted \ + && LC_ALL=C sort /tmp/itworx-rpm-inventory.unsorted > /usr/share/itworx/rpm-inventory.txt \ + && rm /tmp/itworx-rpm-inventory.unsorted \ + && chmod 0444 /usr/share/itworx/rpm-inventory.txt diff --git a/infrastructure/images/contextforge/security-requirements.txt b/infrastructure/images/contextforge/security-requirements.txt new file mode 100644 index 0000000..108f957 --- /dev/null +++ b/infrastructure/images/contextforge/security-requirements.txt @@ -0,0 +1,2 @@ +PyJWT==2.15.0 --hash=sha256:7a3742debf6b879e912dbb9819ceec1594be812452b78c5f2e2dfc56564954f8 +urllib3==2.8.0 --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 diff --git a/infrastructure/images/docker-read-broker/Dockerfile b/infrastructure/images/docker-read-broker/Dockerfile index ff86f02..6dfb2be 100644 --- a/infrastructure/images/docker-read-broker/Dockerfile +++ b/infrastructure/images/docker-read-broker/Dockerfile @@ -6,6 +6,8 @@ RUN sh /tmp/install-base-security-updates.sh \ && rm /tmp/install-base-security-updates.sh ARG GIT_REVISION +ARG GIT_SOURCE_TREE +ARG IMAGE_VERSION ARG SOURCE_DATE_EPOCH ARG BUILD_DATE @@ -20,6 +22,9 @@ RUN chown -R 10002:10002 /opt/broker LABEL org.opencontainers.image.title="itworx-mcp-hub-docker-read-broker" \ org.opencontainers.image.revision="${GIT_REVISION}" \ org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub" \ + org.opencontainers.image.source-tree="${GIT_SOURCE_TREE}" \ + org.opencontainers.image.version="${IMAGE_VERSION}" \ + org.opencontainers.image.source-date-epoch="${SOURCE_DATE_EPOCH}" \ org.opencontainers.image.created="${BUILD_DATE}" \ org.opencontainers.image.vendor="ITWorx" diff --git a/infrastructure/images/observability/Dockerfile b/infrastructure/images/observability/Dockerfile index 94b51d9..e542170 100644 --- a/infrastructure/images/observability/Dockerfile +++ b/infrastructure/images/observability/Dockerfile @@ -6,6 +6,8 @@ RUN sh /tmp/install-base-security-updates.sh \ && rm /tmp/install-base-security-updates.sh ARG GIT_REVISION +ARG GIT_SOURCE_TREE +ARG IMAGE_VERSION ARG SOURCE_DATE_EPOCH ARG BUILD_DATE @@ -17,6 +19,9 @@ RUN chown -R 10001:10001 /opt/hub LABEL org.opencontainers.image.title="itworx-mcp-hub-observability-collector" \ org.opencontainers.image.revision="${GIT_REVISION}" \ org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub" \ + org.opencontainers.image.source-tree="${GIT_SOURCE_TREE}" \ + org.opencontainers.image.version="${IMAGE_VERSION}" \ + org.opencontainers.image.source-date-epoch="${SOURCE_DATE_EPOCH}" \ org.opencontainers.image.created="${BUILD_DATE}" \ org.opencontainers.image.vendor="ITWorx" diff --git a/infrastructure/images/python-service/Dockerfile b/infrastructure/images/python-service/Dockerfile index 5fdbe0b..bd7de08 100644 --- a/infrastructure/images/python-service/Dockerfile +++ b/infrastructure/images/python-service/Dockerfile @@ -18,6 +18,8 @@ RUN sh /tmp/install-base-security-updates.sh \ ARG SERVICE_NAME=itworx-mcp-hub-service ARG GIT_REVISION +ARG GIT_SOURCE_TREE +ARG IMAGE_VERSION ARG SOURCE_DATE_EPOCH ARG BUILD_DATE @@ -49,6 +51,9 @@ RUN chown -R 10001:10001 /opt/hub LABEL org.opencontainers.image.title="itworx-mcp-hub-${SERVICE_NAME}" \ org.opencontainers.image.revision="${GIT_REVISION}" \ org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub" \ + org.opencontainers.image.source-tree="${GIT_SOURCE_TREE}" \ + org.opencontainers.image.version="${IMAGE_VERSION}" \ + org.opencontainers.image.source-date-epoch="${SOURCE_DATE_EPOCH}" \ org.opencontainers.image.created="${BUILD_DATE}" \ org.opencontainers.image.vendor="ITWorx" diff --git a/scripts/build_contextforge_compat_image.sh b/scripts/build_contextforge_compat_image.sh index 8a503bd..5077e3c 100644 --- a/scripts/build_contextforge_compat_image.sh +++ b/scripts/build_contextforge_compat_image.sh @@ -1,13 +1,25 @@ #!/usr/bin/env bash set -Eeuo pipefail +# Candidate-only mode emits a local SHA-scoped image and does not publish or deploy it. +MODE="legacy" +if [[ "$#" -gt 0 ]]; then + [[ "$#" -eq 1 && "$1" == "--release-candidate" ]] || { + echo "FATAL: supported mode is --release-candidate (or no arguments for legacy mode)" >&2 + exit 64 + } + MODE="release-candidate" +fi + ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" MANIFEST="$ROOT/infrastructure/images/contextforge/compat-image.env" CONTRACT="$ROOT/infrastructure/images/contextforge/runtime-contract.json" ENTRYPOINT="$ROOT/infrastructure/images/contextforge/entrypoint.sh" RUNTIME_VERIFIER="$ROOT/scripts/contextforge_runtime_contract.py" +SECURITY_REQUIREMENTS="$ROOT/infrastructure/images/contextforge/security-requirements.txt" +CANDIDATE_RECIPE="$ROOT/scripts/contextforge_candidate_recipe.py" -for required in "$MANIFEST" "$CONTRACT" "$ENTRYPOINT" "$RUNTIME_VERIFIER"; do +for required in "$MANIFEST" "$CONTRACT" "$ENTRYPOINT" "$RUNTIME_VERIFIER" "$SECURITY_REQUIREMENTS"; do [[ -f "$required" ]] || { echo "FATAL: required ContextForge compatibility input is missing: $required" >&2 exit 66 @@ -22,15 +34,87 @@ source "$MANIFEST" : "${CONTEXTFORGE_MCP_SDK:?missing MCP SDK version}" : "${CONTEXTFORGE_LOCAL_IMAGE:?missing local output image}" +if [[ "$MODE" == "release-candidate" ]]; then + [[ -f "$CANDIDATE_RECIPE" ]] || { + echo "FATAL: the source-locked candidate recipe helper is missing" >&2 + exit 66 + } + # Validate every interpolated value before invoking Git, Docker, or Python. + # These fixed-format fields become Docker tags, Git comparisons, and labels. + : "${APPOPS_SOURCE_REPOSITORY:?missing APPOPS_SOURCE_REPOSITORY}" + : "${APPOPS_SOURCE_REVISION:?missing APPOPS_SOURCE_REVISION}" + : "${APPOPS_SOURCE_TREE:?missing APPOPS_SOURCE_TREE}" + : "${APPOPS_BUILD_ID:?missing APPOPS_BUILD_ID}" + : "${APPOPS_BUILD_DATE:?missing APPOPS_BUILD_DATE}" + : "${SOURCE_DATE_EPOCH:?missing SOURCE_DATE_EPOCH}" + : "${GITEA_REPOSITORY:?missing GITEA_REPOSITORY}" + : "${GITEA_COMMIT_SHA:?missing GITEA_COMMIT_SHA}" + + [[ "$APPOPS_SOURCE_REPOSITORY" == "jens/itworx_mcp_hub" ]] || { + echo "FATAL: release-candidate source repository is not the protected Hub repository" >&2 + exit 64 + } + [[ "$GITEA_REPOSITORY" == "$APPOPS_SOURCE_REPOSITORY" ]] || { + echo "FATAL: Gitea and AppOps repositories disagree" >&2 + exit 64 + } + [[ "$APPOPS_SOURCE_REVISION" =~ ^[0-9a-f]{40}$ ]] || { + echo "FATAL: APPOPS_SOURCE_REVISION must be a full lowercase Git SHA" >&2 + exit 64 + } + [[ "$GITEA_COMMIT_SHA" == "$APPOPS_SOURCE_REVISION" ]] || { + echo "FATAL: Gitea and AppOps revisions disagree" >&2 + exit 64 + } + [[ "$APPOPS_SOURCE_TREE" =~ ^[0-9a-f]{40}$ ]] || { + echo "FATAL: APPOPS_SOURCE_TREE must be a full lowercase Git tree SHA" >&2 + exit 64 + } + [[ "$APPOPS_BUILD_ID" == "${APPOPS_SOURCE_REVISION:0:12}" ]] || { + echo "FATAL: APPOPS_BUILD_ID must be the first 12 revision characters" >&2 + exit 64 + } + [[ "$APPOPS_BUILD_DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$ ]] || { + echo "FATAL: APPOPS_BUILD_DATE must be canonical UTC with millisecond precision" >&2 + exit 64 + } + [[ "$SOURCE_DATE_EPOCH" =~ ^[1-9][0-9]{0,11}$ ]] || { + echo "FATAL: SOURCE_DATE_EPOCH must be a canonical positive integer of at most 12 digits" >&2 + exit 64 + } + [[ "$CONTEXTFORGE_LOCAL_IMAGE" == "itworx-mcp-hub/contextforge-schema:12d4a0c7789c" ]] || { + echo "FATAL: compatibility manifest image name is not the reviewed Hub image" >&2 + exit 64 + } +fi + command -v git >/dev/null 2>&1 || { echo "FATAL: git is required" >&2; exit 69; } command -v docker >/dev/null 2>&1 || { echo "FATAL: docker is required" >&2; exit 69; } command -v python3 >/dev/null 2>&1 || { echo "FATAL: python3 is required" >&2; exit 69; } +if [[ "$MODE" == "release-candidate" ]]; then + python3 - "$APPOPS_BUILD_DATE" <<'PY' +from datetime import datetime +import sys + +try: + datetime.strptime(sys.argv[1], "%Y-%m-%dT%H:%M:%S.%fZ") +except ValueError as exc: + raise SystemExit("FATAL: APPOPS_BUILD_DATE is not a valid UTC calendar timestamp") from exc +PY +fi + TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT SRC="$TMP/contextforge" -BASE_IMAGE="${CONTEXTFORGE_LOCAL_IMAGE}-upstream" -STAGED_IMAGE="${CONTEXTFORGE_LOCAL_IMAGE}-staged" +if [[ "$MODE" == "release-candidate" ]]; then + CANDIDATE_IMAGE="itworx-mcp-hub/contextforge-schema:${APPOPS_SOURCE_REVISION}" + BASE_IMAGE="itworx-mcp-hub/contextforge-schema:${APPOPS_SOURCE_REVISION}-upstream" + STAGED_IMAGE="itworx-mcp-hub/contextforge-schema:${APPOPS_SOURCE_REVISION}-staged" +else + BASE_IMAGE="${CONTEXTFORGE_LOCAL_IMAGE}-upstream" + STAGED_IMAGE="${CONTEXTFORGE_LOCAL_IMAGE}-staged" +fi REVISION_PREFIX="${CONTEXTFORGE_ALEMBIC_HEAD}_" MCP_REQUIREMENT="mcp==${CONTEXTFORGE_MCP_SDK}" @@ -70,12 +154,41 @@ CONTRACT_REQUIRED_SYMBOL="$(read_contract requiredSymbol)" CONTRACT_SHA256="$(sha256_file "$CONTRACT")" ENTRYPOINT_SHA256="$(sha256_file "$ENTRYPOINT")" VERIFIER_SHA256="$(sha256_file "$RUNTIME_VERIFIER")" +SECURITY_REQUIREMENTS_SHA256="$(sha256_file "$SECURITY_REQUIREMENTS")" [[ "$CONTRACT_SOURCE_COMMIT" == "$CONTEXTFORGE_UPSTREAM_COMMIT" ]] || fail "contract source commit disagrees with compatibility manifest" [[ "$CONTRACT_ALEMBIC_HEAD" == "$CONTEXTFORGE_ALEMBIC_HEAD" ]] || fail "contract Alembic head disagrees with compatibility manifest" [[ "$CONTRACT_MCP_SDK" == "$CONTEXTFORGE_MCP_SDK" ]] || fail "contract MCP SDK disagrees with compatibility manifest" [[ "$CONTRACT_REQUIRED_SYMBOL" == "McpError" ]] || fail "unsupported required MCP symbol: $CONTRACT_REQUIRED_SYMBOL" +if [[ "$MODE" == "release-candidate" ]]; then + [[ -z "$(git -C "$ROOT" status --porcelain --untracked-files=all)" ]] || fail "Hub source checkout has changes" + [[ "$(git -C "$ROOT" rev-parse HEAD)" == "$APPOPS_SOURCE_REVISION" ]] || fail "checked-out Hub revision differs from frozen release metadata" + [[ "$(git -C "$ROOT" rev-parse 'HEAD^{tree}')" == "$APPOPS_SOURCE_TREE" ]] || fail "checked-out Hub source tree differs from frozen release metadata" + [[ "$(git -C "$ROOT" show -s --format=%ct HEAD)" == "$SOURCE_DATE_EPOCH" ]] || fail "source date epoch differs from frozen release metadata" + + ensure_candidate_tag_absent() { + local image="$1" + local diagnostic="$TMP/docker-image-inspect.err" + if docker image inspect "$image" >/dev/null 2>"$diagnostic"; then + fail "release-candidate image tag already exists: $image" + fi + local error_line + IFS= read -r error_line < "$diagnostic" || true + case "$error_line" in + *"No such image"*|*"no such image"*|*"No such object"*|*"no such object"*|*"reference does not exist"*) ;; + *) fail "could not prove release-candidate image tag is absent: $image" ;; + esac + } + + # Never build over or retag an existing revision-scoped image. Docker errors + # other than its explicit not-found response fail closed. + ensure_candidate_tag_absent "$BASE_IMAGE" + ensure_candidate_tag_absent "$STAGED_IMAGE" + ensure_candidate_tag_absent "$CANDIDATE_IMAGE" + +fi + echo "=== FETCH EXACT CONTEXTFORGE SOURCE ===" git init -q "$SRC" git -C "$SRC" remote add origin https://github.com/IBM/mcp-context-forge.git @@ -97,13 +210,28 @@ echo "contract sha : $CONTRACT_SHA256" echo echo "=== BUILD UPSTREAM SOURCE IMAGE ===" -docker build --pull=false -f "$SRC/Containerfile" -t "$BASE_IMAGE" "$SRC" +if [[ "$MODE" == "release-candidate" ]]; then + python3 "$CANDIDATE_RECIPE" prepare --source-dir "$SRC" + BUILD_ARG_FILE="$TMP/contextforge-build-args.txt" + python3 "$CANDIDATE_RECIPE" build-args > "$BUILD_ARG_FILE" + mapfile -t CANDIDATE_BUILD_ARGS < "$BUILD_ARG_FILE" + [[ "${#CANDIDATE_BUILD_ARGS[@]}" -eq 9 ]] || fail "candidate base/build argument contract is incomplete" + CANDIDATE_DOCKER_ARGS=() + for build_arg in "${CANDIDATE_BUILD_ARGS[@]}"; do + [[ "$build_arg" =~ ^[A-Z_]+=([A-Za-z0-9./:@_-]+)$ ]] || fail "candidate build argument is malformed" + CANDIDATE_DOCKER_ARGS+=(--build-arg "$build_arg") + done + docker build --pull=false --platform linux/amd64 "${CANDIDATE_DOCKER_ARGS[@]}" -f "$SRC/Containerfile.itworx-candidate" -t "$BASE_IMAGE" "$SRC" +else + docker build --pull=false -f "$SRC/Containerfile" -t "$BASE_IMAGE" "$SRC" +fi BASE_USER="$(docker image inspect -f '{{.Config.User}}' "$BASE_IMAGE")" LAYER="$TMP/Containerfile.compat" cp "$CONTRACT" "$TMP/contextforge-runtime-contract.json" cp "$ENTRYPOINT" "$TMP/contextforge-entrypoint.sh" cp "$RUNTIME_VERIFIER" "$TMP/contextforge-runtime-contract.py" +cp "$SECURITY_REQUIREMENTS" "$TMP/contextforge-security-requirements.txt" { echo "FROM $BASE_IMAGE" @@ -111,6 +239,7 @@ cp "$RUNTIME_VERIFIER" "$TMP/contextforge-runtime-contract.py" echo 'COPY contextforge-runtime-contract.json /opt/itworx/contextforge-runtime-contract.json' echo 'COPY contextforge-entrypoint.sh /opt/itworx/contextforge-entrypoint.sh' echo 'COPY contextforge-runtime-contract.py /opt/itworx/contextforge-runtime-contract.py' + echo 'COPY contextforge-security-requirements.txt /opt/itworx/contextforge-security-requirements.txt' echo 'RUN chmod 0555 /opt/itworx/contextforge-entrypoint.sh /opt/itworx/contextforge-runtime-contract.py' printf 'LABEL io.itworx.contextforge.contract-sha256="%s" \\\n' "$CONTRACT_SHA256" printf ' io.itworx.contextforge.entrypoint-sha256="%s" \\\n' "$ENTRYPOINT_SHA256" @@ -118,8 +247,24 @@ cp "$RUNTIME_VERIFIER" "$TMP/contextforge-runtime-contract.py" printf ' io.itworx.contextforge.source-commit="%s" \\\n' "$CONTEXTFORGE_UPSTREAM_COMMIT" printf ' io.itworx.contextforge.alembic-head="%s" \\\n' "$CONTEXTFORGE_ALEMBIC_HEAD" printf ' io.itworx.contextforge.mcp-sdk="%s"\n' "$CONTEXTFORGE_MCP_SDK" - printf 'RUN /app/.venv/bin/python -m pip install --no-cache-dir --upgrade %q \\\n' "$MCP_REQUIREMENT" - printf '%s\n' ' && /app/.venv/bin/python -c "import importlib.metadata as md; from mcp import McpError; assert md.version(\"mcp\") == \"'"${CONTEXTFORGE_MCP_SDK}"'\"; print(\"MCP SDK\", md.version(\"mcp\"), \"McpError OK\")"' + printf 'LABEL io.itworx.contextforge.security-requirements-sha256="%s"\n' "$SECURITY_REQUIREMENTS_SHA256" + if [[ "$MODE" == "release-candidate" ]]; then + printf 'LABEL org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub" \\\n' + printf ' org.opencontainers.image.revision="%s" \\\n' "$APPOPS_SOURCE_REVISION" + printf ' org.opencontainers.image.source-tree="%s" \\\n' "$APPOPS_SOURCE_TREE" + printf ' org.opencontainers.image.version="%s" \\\n' "$APPOPS_BUILD_ID" + printf ' org.opencontainers.image.source-date-epoch="%s" \\\n' "$SOURCE_DATE_EPOCH" + printf ' org.opencontainers.image.created="%s"\n' "$APPOPS_BUILD_DATE" + fi + if [[ "$MODE" == "release-candidate" ]]; then + echo 'RUN /app/.venv/bin/python -c "import importlib.metadata as md; import importlib.util; from mcp import McpError; assert md.version(\"mcp\") == \"1.30.0\"; assert md.version(\"PyJWT\") == \"2.15.0\"; assert md.version(\"urllib3\") == \"2.8.0\"; assert all(importlib.util.find_spec(name) is None for name in (\"pip\", \"setuptools\", \"wheel\", \"uv\"))"' + else + printf 'RUN /app/.venv/bin/python -m pip install --no-cache-dir --upgrade %q \\\n' "$MCP_REQUIREMENT" + printf '%s\n' ' && /app/.venv/bin/python -c "import importlib.metadata as md; from mcp import McpError; assert md.version(\"mcp\") == \"'"${CONTEXTFORGE_MCP_SDK}"'\"; print(\"MCP SDK\", md.version(\"mcp\"), \"McpError OK\")"' + echo 'RUN /app/.venv/bin/python -m pip install --no-cache-dir --no-deps --require-hashes --only-binary=:all: -r /opt/itworx/contextforge-security-requirements.txt \' + echo ' && /app/.venv/bin/python -m pip check \' + echo ' && /app/.venv/bin/python -c "import importlib.metadata as md; assert md.version(\"PyJWT\") == \"2.15.0\"; assert md.version(\"urllib3\") == \"2.8.0\""' + fi if [[ -n "$BASE_USER" ]]; then printf 'USER %s\n' "$BASE_USER" fi @@ -142,6 +287,7 @@ embedded_sha() { [[ "$(embedded_sha /opt/itworx/contextforge-runtime-contract.json)" == "$CONTRACT_SHA256" ]] || fail "embedded runtime contract digest mismatch" [[ "$(embedded_sha /opt/itworx/contextforge-entrypoint.sh)" == "$ENTRYPOINT_SHA256" ]] || fail "embedded entrypoint digest mismatch" [[ "$(embedded_sha /opt/itworx/contextforge-runtime-contract.py)" == "$VERIFIER_SHA256" ]] || fail "embedded verifier digest mismatch" +[[ "$(embedded_sha /opt/itworx/contextforge-security-requirements.txt)" == "$SECURITY_REQUIREMENTS_SHA256" ]] || fail "embedded security requirements digest mismatch" label_value() { docker image inspect -f "{{ index .Config.Labels \"$1\" }}" "$STAGED_IMAGE" @@ -152,19 +298,43 @@ label_value() { [[ "$(label_value io.itworx.contextforge.source-commit)" == "$CONTEXTFORGE_UPSTREAM_COMMIT" ]] || fail "source label mismatch" [[ "$(label_value io.itworx.contextforge.alembic-head)" == "$CONTEXTFORGE_ALEMBIC_HEAD" ]] || fail "Alembic label mismatch" [[ "$(label_value io.itworx.contextforge.mcp-sdk)" == "$CONTEXTFORGE_MCP_SDK" ]] || fail "MCP SDK label mismatch" +[[ "$(label_value io.itworx.contextforge.security-requirements-sha256)" == "$SECURITY_REQUIREMENTS_SHA256" ]] || fail "security requirements label mismatch" +if [[ "$MODE" == "release-candidate" ]]; then + [[ "$(label_value org.opencontainers.image.source)" == "https://gitea.example.com/jens/itworx-mcp-hub" ]] || fail "OCI source label mismatch" + [[ "$(label_value org.opencontainers.image.revision)" == "$APPOPS_SOURCE_REVISION" ]] || fail "OCI revision label mismatch" + [[ "$(label_value org.opencontainers.image.source-tree)" == "$APPOPS_SOURCE_TREE" ]] || fail "OCI source tree label mismatch" + [[ "$(label_value org.opencontainers.image.version)" == "$APPOPS_BUILD_ID" ]] || fail "OCI version label mismatch" + [[ "$(label_value org.opencontainers.image.source-date-epoch)" == "$SOURCE_DATE_EPOCH" ]] || fail "OCI source date epoch label mismatch" + [[ "$(label_value org.opencontainers.image.created)" == "$APPOPS_BUILD_DATE" ]] || fail "OCI created label mismatch" +fi echo "MCP SDK : $CONTEXTFORGE_MCP_SDK" echo "Alembic head : $IMAGE_HEAD" echo "Contract SHA : $CONTRACT_SHA256" -docker tag "$STAGED_IMAGE" "$CONTEXTFORGE_LOCAL_IMAGE" +if [[ "$MODE" == "release-candidate" ]]; then + # Recheck immediately before publishing. There is intentionally no cleanup or + # retagging of any static compatibility image in candidate mode. + ensure_candidate_tag_absent "$CANDIDATE_IMAGE" + docker tag "$STAGED_IMAGE" "$CANDIDATE_IMAGE" +else + docker tag "$STAGED_IMAGE" "$CONTEXTFORGE_LOCAL_IMAGE" +fi echo echo "======================================================" -echo " SUCCESS: CONTEXTFORGE COMPATIBILITY IMAGE VERIFIED" +if [[ "$MODE" == "release-candidate" ]]; then + echo " CANDIDATE IMAGE BUILT AND VERIFIED (NOT APPROVED OR DEPLOYED)" +else + echo " SUCCESS: CONTEXTFORGE COMPATIBILITY IMAGE VERIFIED" +fi echo " source commit : $CONTEXTFORGE_UPSTREAM_COMMIT" echo " Alembic head : $CONTEXTFORGE_ALEMBIC_HEAD" echo " MCP SDK : $CONTEXTFORGE_MCP_SDK" echo " contract SHA : $CONTRACT_SHA256" -echo " local image : $CONTEXTFORGE_LOCAL_IMAGE" +if [[ "$MODE" == "release-candidate" ]]; then + echo " candidate tag : $CANDIDATE_IMAGE" +else + echo " local image : $CONTEXTFORGE_LOCAL_IMAGE" +fi echo "======================================================" diff --git a/scripts/check_cve_exceptions.py b/scripts/check_cve_exceptions.py index 529eb37..3f0ab2a 100644 --- a/scripts/check_cve_exceptions.py +++ b/scripts/check_cve_exceptions.py @@ -12,9 +12,8 @@ Specifically fails on: grant); - a matching exception that has expired (expiryDate in the past) or is past its mandatory rescan date; -- a matching exception whose recorded affectedImageDigests disagree with - --image-digests for any service both documents name (the digest changed -- - the exception was reviewed against a build that no longer exists); +- a matching exception without a valid image binding for each affected + service, or whose binding differs from the current image inventory; - an exception missing any required field. See docs/09-release/CVE_EXCEPTIONS.md for the full policy and @@ -25,6 +24,7 @@ context. This script has no "--force"/override flag by design. from __future__ import annotations import argparse +import re import sys from datetime import date from pathlib import Path @@ -36,6 +36,8 @@ if str(_ROOT_DIR) not in sys.path: from scripts.packlib import load_json # noqa: E402 +_SHA256_IMAGE_ID = re.compile(r"^sha256:[0-9a-f]{64}$") + REQUIRED_FIELDS = ( "id", "cve", @@ -57,24 +59,134 @@ REQUIRED_FIELDS = ( ) -def _findings(scan: dict[str, Any]) -> list[tuple[str, str, str]]: - """Return (package, cve, severity) for every CRITICAL/HIGH finding.""" +def _findings(scan: dict[str, Any]) -> list[tuple[dict[str, Any], str]]: + """Return (scan row, CVE) pairs for every CRITICAL/HIGH finding.""" findings = [] for row in scan.get("unresolvedCriticalOrHigh", []): for cve in row["cves"]: - findings.append((row["package"], cve, row.get("severity", "UNKNOWN"))) + findings.append((row, cve)) return findings +def _current_image_inventory( + image_digests: Any, +) -> tuple[dict[str, dict[str, str]], list[str]]: + """Validate immutable current image bindings while retaining optional tags.""" + errors: list[str] = [] + if not isinstance(image_digests, dict) or not image_digests: + return {}, ["IMAGE INVENTORY INVALID: --image-digests must contain a non-empty images map"] + + inventory: dict[str, dict[str, str]] = {} + for service, entry in image_digests.items(): + if not isinstance(service, str) or not service.strip(): + errors.append("IMAGE INVENTORY INVALID: every image service needs a non-empty name") + continue + if not isinstance(entry, dict): + errors.append(f"IMAGE INVENTORY INVALID: {service} must be an image evidence object") + continue + + image_id = entry.get("imageId") + if not isinstance(image_id, str) or not _SHA256_IMAGE_ID.fullmatch(image_id): + errors.append(f"CURRENT IMAGE ID INVALID: {service} needs a sha256 imageId") + continue + + if "digest" in entry: + digest = entry.get("digest") + if not isinstance(digest, str) or not _SHA256_IMAGE_ID.fullmatch(digest): + errors.append(f"CURRENT IMAGE DIGEST INVALID: {service} has a malformed digest") + continue + binding = digest + else: + # Older v1 inventory evidence records the immutable local image ID + # instead of a registry manifest digest. + binding = image_id + + tag = entry.get("tag") + if tag is not None and (not isinstance(tag, str) or not tag.strip()): + errors.append(f"IMAGE INVENTORY INVALID: {service} has a malformed tag") + continue + inventory[service] = {"binding": binding, "imageId": image_id} + if isinstance(tag, str): + inventory[service]["tag"] = tag + + if not inventory: + errors.append("IMAGE INVENTORY INVALID: no valid current image bindings were supplied") + return inventory, errors + + +def _affected_services( + row: dict[str, Any], inventory: dict[str, dict[str, str]] +) -> tuple[list[str], list[str]]: + """Resolve scan image tags to services, or use the legacy full inventory.""" + if "affectedImages" not in row: + # Legacy v1 scan rows had no affectedImages field. Requiring the + # exception to cover the whole supplied inventory is the safe fallback. + return list(inventory), [] + + affected_images = row.get("affectedImages") + if ( + not isinstance(affected_images, list) + or not affected_images + or any(not isinstance(image, str) or not image.strip() for image in affected_images) + or len(set(affected_images)) != len(affected_images) + ): + return [], ["SCAN AFFECTED IMAGES INVALID: expected a non-empty list of unique image tags"] + + services_by_tag: dict[str, list[str]] = {} + for service, evidence in inventory.items(): + tag = evidence.get("tag") + if tag: + services_by_tag.setdefault(tag, []).append(service) + + services: list[str] = [] + errors: list[str] = [] + for image in affected_images: + matches = services_by_tag.get(image, []) + if not matches: + errors.append( + f"AFFECTED IMAGE UNBOUND: scan image {image} has no current service binding" + ) + elif len(matches) > 1: + errors.append(f"AFFECTED IMAGE AMBIGUOUS: scan image {image} maps to multiple services") + else: + services.append(matches[0]) + return services, errors + + def check( scan: dict[str, Any], exceptions_doc: dict[str, Any], - image_digests: dict[str, str] | None, + image_digests: dict[str, Any] | None, today: date, ) -> list[str]: errors: list[str] = [] - by_key: dict[tuple[str, str], dict[str, Any]] = {} + image_inventory: dict[str, dict[str, str]] = {} + if image_digests is not None: + image_inventory, inventory_errors = _current_image_inventory(image_digests) + errors.extend(inventory_errors) + + by_key: dict[tuple[str, str, str], dict[str, Any]] = {} + raw_by_key: dict[tuple[str, str, str], dict[str, Any]] = {} + conflicting_keys: set[tuple[str, str, str]] = set() for exception in exceptions_doc.get("exceptions", []): + if not isinstance(exception, dict): + errors.append("exception entry must be an object") + continue + package = exception.get("package") + cve = exception.get("cve") + version = exception.get("installedVersion") + key: tuple[str, str, str] | None = None + if isinstance(package, str) and isinstance(cve, str) and isinstance(version, str): + if package and cve and version: + key = (package, cve, version) + if key is not None and key in raw_by_key and raw_by_key[key] != exception: + conflicting_keys.add(key) + errors.append( + f"CONFLICTING DUPLICATE: multiple exception entries disagree for {package}/{cve}" + ) + elif key is not None: + raw_by_key.setdefault(key, exception) + missing = [field for field in REQUIRED_FIELDS if not exception.get(field)] # riskOwner/approver/compensatingControls may be legitimately empty # only while approvalStatus is "pending" -- once "approved" they are @@ -87,12 +199,44 @@ def check( f"exception {exception.get('id', '')} is missing required fields: {missing}" ) continue - key = (exception["package"], exception["cve"]) - by_key[key] = exception + if key is not None: + by_key.setdefault(key, exception) - for package, cve, severity in _findings(scan): - key = (package, cve) - exception = by_key.get(key) + for key in conflicting_keys: + by_key.pop(key, None) + + by_pair: dict[tuple[str, str], list[dict[str, Any]]] = {} + for (package, cve, _version), exception in by_key.items(): + by_pair.setdefault((package, cve), []).append(exception) + + for row, cve in _findings(scan): + package = row["package"] + severity = row.get("severity", "UNKNOWN") + reviewed = by_pair.get((package, cve), []) + if "installedVersion" in row: + scanned_version = row.get("installedVersion") + if not isinstance(scanned_version, str) or not scanned_version: + errors.append( + f"SCAN VERSION MISSING: {package}/{cve} has no valid installedVersion" + ) + continue + exception = by_key.get((package, cve, scanned_version)) + if exception is None and reviewed: + errors.append( + f"VERSION MISMATCH: {package}/{cve} scan found {scanned_version}, " + "but no exception reviewed that exact version" + ) + continue + else: + # Existing v1 evidence may omit the installed version. It remains + # usable only when one unambiguous reviewed version exists. + if len(reviewed) > 1: + errors.append( + f"AMBIGUOUS VERSION: {package}/{cve} legacy scan omits installedVersion " + "and multiple versions were reviewed" + ) + continue + exception = reviewed[0] if reviewed else None if exception is None: errors.append( f"UNAPPROVED: {severity} finding {package}/{cve} has no exception recorded at all" @@ -128,11 +272,38 @@ def check( f"{rescan_due.isoformat()} has passed" ) - if image_digests: + if image_digests is not None: + legacy_full_inventory = "affectedImages" not in row + services, scope_errors = _affected_services(row, image_inventory) + errors.extend(scope_errors) recorded = exception.get("affectedImageDigests", {}) - for service, current_digest in image_digests.items(): + if not isinstance(recorded, dict): + errors.append( + f"IMAGE BINDING INVALID: {package}/{cve} affectedImageDigests must be an object" + ) + continue + if legacy_full_inventory and set(recorded) != set(image_inventory): + errors.append( + f"IMAGE INVENTORY SCOPE CHANGED: {package}/{cve} uses a legacy scan " + "without affectedImages and its reviewed service set does not exactly " + "match the current full inventory" + ) + for service in services: + current_digest = image_inventory[service]["binding"] recorded_digest = recorded.get(service) - if recorded_digest and recorded_digest != current_digest: + if recorded_digest is None or recorded_digest == "": + errors.append( + f"IMAGE BINDING MISSING: {package}/{cve} has no valid reviewed digest " + f"for {service}" + ) + elif not isinstance(recorded_digest, str) or not _SHA256_IMAGE_ID.fullmatch( + recorded_digest + ): + errors.append( + f"IMAGE BINDING INVALID: {package}/{cve} has a malformed reviewed digest " + f"for {service}" + ) + elif recorded_digest != current_digest: errors.append( f"DIGEST CHANGED: {package}/{cve} exception recorded " f"{service}={recorded_digest}, but the current build is " @@ -151,19 +322,16 @@ def main() -> int: "--image-digests", type=Path, help="evidence//image-digests.json; when given, exceptions whose " - "recorded digest for a named service disagrees with the current build fail.", + "current image IDs or reviewed bindings are missing, malformed, or changed fail.", ) args = parser.parse_args() scan = load_json(args.scan_summary) exceptions_doc = load_json(args.exceptions) - image_digests: dict[str, str] | None = None + image_digests: dict[str, Any] | None = None if args.image_digests: digest_doc = load_json(args.image_digests) - image_digests = { - service: (entry.get("digest") or entry.get("imageId")) - for service, entry in digest_doc.get("images", {}).items() - } + image_digests = digest_doc.get("images", {}) errors = check(scan, exceptions_doc, image_digests, date.today()) if errors: diff --git a/scripts/contextforge_candidate_recipe.py b/scripts/contextforge_candidate_recipe.py new file mode 100644 index 0000000..5a1235e --- /dev/null +++ b/scripts/contextforge_candidate_recipe.py @@ -0,0 +1,497 @@ +#!/usr/bin/env python3 +"""Prepare a hash-locked, source-checked ContextForge candidate build recipe.""" + +# Exact image references, source anchors, and Dockerfile instructions exceed 100 columns. +# ruff: noqa: E501 + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import re +import shutil +import subprocess +import tempfile +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[1] +LOCK_DIR = ROOT / "infrastructure" / "images" / "contextforge" / "candidate" +LOCK_MANIFEST = LOCK_DIR / "locks.json" +PRODUCTION_LOCK = LOCK_DIR / "production-requirements.lock" +BUILD_TOOLS_LOCK = LOCK_DIR / "build-tools.lock" + +UPSTREAM_COMMIT = "1e58a0434a78bbdbe1e975abeb0bed1043ff14c1" +UV_VERSION = "0.12.10" +LOCK_TIMESTAMP = "2026-10-02T00:00:00Z" +GLOBAL_LOCK_CUTOFF = "2026-09-22T00:00:00Z" +EXTRAS = ("redis", "postgres", "observability", "granian", "plugins", "llmchat") +CONSTRAINTS = ("mcp==1.30.0", "PyJWT==2.15.0", "urllib3==2.8.0") +BUILD_TOOLS = ("pip==26.2.1", "setuptools==82.0.1", "wheel==0.46.2") + +SOURCE_HASHES = { + "Containerfile": "2ab0187f3a63eced093a1dec3141ec3e30ef879eb7de0dfd9e4bc5fce33f64f2", + "pyproject.toml": "cd365566f922546e569c65475d78cd53e5d49f10df2c82534efa607ffcf55e4b", + "uv.lock": "c5297a4701e4a0ecb9903bd3f4aa1c8cf40cf4e5568880fcb8f4078703589df1", +} + +BASE_IMAGES = ( + { + "arg": "UBI_BASE", + "sourceTag": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder:sha256-c6bb70a7a340ed1047e7cb934aa55b086ee68a5eb9ba6b2ad2e6825c7184a3e8", + "reference": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder@sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483", + "parentReference": "registry.access.redhat.com/ubi10@sha256:1e32d829a4de713a835d702d3bc05f0c448a08590796281b7dec181912e9aeb8", + "imageId": "sha256:c6bb70a7a340ed1047e7cb934aa55b086ee68a5eb9ba6b2ad2e6825c7184a3e8", + "rpmInventorySha256": "6a8c0d4f66fc5a38008541ad15614711e7f536cd18c70c02d2eb0691552b88b0", + }, + { + "arg": "NODEJS_IMAGE", + "sourceTag": "registry.access.redhat.com/ubi10/nodejs-24:1784092609", + "reference": "registry.access.redhat.com/ubi10/nodejs-24@sha256:0f110e68682f4a49188ba4db786eb4f207b119420913fc76bb5ba9512ffbef06", + }, + { + "arg": "UBI_MINIMAL", + "sourceTag": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime:sha256-fd94e2643dfc0401790d0528d3740bb2f0c04709e743f6c774a4e8a38606e40b", + "reference": "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236", + "parentReference": "registry.access.redhat.com/ubi10/ubi-minimal@sha256:afbfb5c18c628796fed3ad3122dcf096e0b2dec9dad3fbabe8620de06bc6f078", + "imageId": "sha256:fd94e2643dfc0401790d0528d3740bb2f0c04709e743f6c774a4e8a38606e40b", + "rpmInventorySha256": "a74d60ecb43d1aa561874305d1a9c62ac35c99a14c1dc275ba1656f14df7d034", + }, +) + +LOCK_OUTPUT_NAMES = { + "production-requirements.lock": "contextforge-candidate-production-requirements.txt", + "build-tools.lock": "contextforge-candidate-build-tools.txt", +} + +INSTALL_START = ( + "RUN set -euo pipefail \\\n" + " && . /etc/profile.d/use-openssl.sh \\\n" + " && python3 -m venv /app/.venv" +) +INSTALL_END = "\n# ----------------------------------------------------------------------------\n# Copy Rust runtime binaries and frontend static output from their builder\n" +LOCK_COPY_ANCHOR = "COPY pyproject.toml /app/\n" + + +class CandidateRecipeError(ValueError): + """Raised when a candidate recipe input is absent or differs from contract.""" + + +def sha256_hex(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def _require_file(path: Path) -> bytes: + if not path.is_file() or path.is_symlink(): + raise CandidateRecipeError(f"required regular file is missing: {path.name}") + return path.read_bytes() + + +def verify_upstream_source( + source_dir: Path, expected_hashes: dict[str, str] | None = None +) -> dict[str, bytes]: + files: dict[str, bytes] = {} + for name, expected in (expected_hashes or SOURCE_HASHES).items(): + content = _require_file(source_dir / name) + actual = sha256_hex(content) + if actual != expected: + raise CandidateRecipeError(f"pinned upstream {name} hash mismatch") + files[name] = content + return files + + +def _table_range(text: str, table: str) -> tuple[int, int, str]: + header = f"[{table}]" + matches = list(re.finditer(rf"(?m)^{re.escape(header)}\s*$", text)) + if len(matches) != 1: + raise CandidateRecipeError(f"expected exactly one TOML table {header}") + start = matches[0].start() + next_header = re.search(r"(?m)^\[[^\r\n]+\]\s*$", text[matches[0].end() :]) + end = matches[0].end() + next_header.start() if next_header else len(text) + return start, end, text[start:end] + + +def candidate_pyproject(source_text: str) -> str: + """Apply the narrowly scoped candidate-only dependency resolution policy.""" + try: + import tomllib + + source = tomllib.loads(source_text) + except (ImportError, ValueError) as exc: + raise CandidateRecipeError("upstream pyproject is not valid TOML") from exc + + uv = source.get("tool", {}).get("uv", {}) + if uv.get("exclude-newer") != "10 days": + raise CandidateRecipeError("upstream uv freshness policy changed") + groups = source.get("dependency-groups", {}) + if set(groups) != {"dev"}: + raise CandidateRecipeError("upstream dependency-group shape changed") + sources = uv.get("sources", {}) + if sources != { + "compliance-reference-server": { + "path": "mcp-servers/python/compliance_reference_server", + "editable": True, + } + }: + raise CandidateRecipeError("upstream uv source override shape changed") + + text = source_text.replace("\r\n", "\n") + source_start, source_end, source_table = _table_range(text, "tool.uv.sources") + source_lines = source_table.strip().splitlines() + expected_source_lines = [ + "[tool.uv.sources]", + 'compliance-reference-server = { path = "mcp-servers/python/compliance_reference_server", editable = true }', + ] + if source_lines[:2] != expected_source_lines or any( + line.strip() and not line.lstrip().startswith("#") for line in source_lines[2:] + ): + raise CandidateRecipeError("upstream local development source table changed") + text = text[:source_start] + text[source_end:] + + group_start, group_end, _group_table = _table_range(text, "dependency-groups") + text = text[:group_start] + text[group_end:] + + uv_anchor = 'exclude-newer = "10 days"\n' + if text.count(uv_anchor) != 1: + raise CandidateRecipeError("upstream uv table anchor changed") + text = text.replace( + uv_anchor, + f'exclude-newer = "{GLOBAL_LOCK_CUTOFF}"\n' + + 'constraint-dependencies = ["mcp==1.30.0", "PyJWT==2.15.0", "urllib3==2.8.0"]\n', + 1, + ) + + package_table = "[tool.uv.exclude-newer-package]\n" + if text.count(package_table) != 1: + raise CandidateRecipeError("upstream package freshness table is missing") + package_overrides = "".join( + f'{name.lower()} = "{LOCK_TIMESTAMP}"\n' + for name in ("mcp", "pyjwt", "urllib3", "pip", "setuptools", "wheel") + ) + text = text.replace(package_table, package_table + package_overrides, 1) + + text = text.rstrip() + "\n\n[dependency-groups]\nbuild-tools = [\n" + text += "".join(f' "{requirement}",\n' for requirement in BUILD_TOOLS) + text += "]\n" + try: + tomllib.loads(text) + except ValueError as exc: + raise CandidateRecipeError("generated candidate pyproject is invalid TOML") from exc + return text + + +def _run_uv(args: list[str], cwd: Path) -> None: + completed = subprocess.run( # noqa: S603 - fixed executable and structured arguments + [_uv_binary(), *args], + cwd=cwd, + check=False, + capture_output=True, + text=True, + ) + if completed.returncode: + diagnostic = completed.stderr.strip().splitlines()[-1:] or ["uv command failed"] + raise CandidateRecipeError(diagnostic[0]) + + +def _uv_binary() -> str: + binary = shutil.which("uv") + if binary is None or not Path(binary).is_absolute(): + raise CandidateRecipeError("uv executable is missing or non-absolute") + return binary + + +def generate_locks(source_dir: Path, output_dir: Path) -> None: + """Regenerate locks from the exact upstream project using the pinned uv tool.""" + source = verify_upstream_source(source_dir) + version = subprocess.run( # noqa: S603 - fixed version check before lock generation + [_uv_binary(), "--version"], check=False, capture_output=True, text=True + ) + version_parts = version.stdout.strip().split() + if version.returncode or version_parts[:2] != ["uv", UV_VERSION]: + raise CandidateRecipeError(f"lock generation requires uv {UV_VERSION}") + + output_dir = output_dir.resolve() + output_dir.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(prefix="contextforge-lock-") as temp_name: + temp = Path(temp_name) + (temp / "pyproject.toml").write_text( + candidate_pyproject(source["pyproject.toml"].decode("utf-8")), encoding="utf-8" + ) + (temp / "uv.lock").write_bytes(source["uv.lock"]) + _run_uv(["lock", "--project", str(temp)], temp) + + production_path = output_dir / "production-requirements.lock" + build_path = output_dir / "build-tools.lock" + _run_uv( + [ + "export", + "--project", + str(temp), + "--format", + "requirements-txt", + "--locked", + "--no-dev", + "--no-default-groups", + "--no-emit-project", + "--no-header", + "--no-annotate", + *[value for extra in EXTRAS for value in ("--extra", extra)], + "--output-file", + str(production_path), + ], + temp, + ) + _run_uv( + [ + "export", + "--project", + str(temp), + "--format", + "requirements-txt", + "--locked", + "--only-group", + "build-tools", + "--no-default-groups", + "--no-emit-project", + "--no-header", + "--no-annotate", + "--output-file", + str(build_path), + ], + temp, + ) + + manifest = _make_manifest( + source_hashes=SOURCE_HASHES, + production_hash=sha256_hex(_require_file(output_dir / "production-requirements.lock")), + build_tools_hash=sha256_hex(_require_file(output_dir / "build-tools.lock")), + ) + _write_atomic(output_dir / "locks.json", _canonical_json(manifest)) + + +def _make_manifest( + *, source_hashes: dict[str, str], production_hash: str, build_tools_hash: str +) -> dict[str, Any]: + return { + "schema": "itworx.contextforge-candidate-locks/v1", + "upstreamCommit": UPSTREAM_COMMIT, + "upstreamFiles": source_hashes, + "uvVersion": UV_VERSION, + "python": "3.12", + "platform": "linux/amd64", + "extras": list(EXTRAS), + "constraints": list(CONSTRAINTS), + "buildTools": list(BUILD_TOOLS), + "globalExcludeNewerUtc": GLOBAL_LOCK_CUTOFF, + "freshnessOverrideUtc": LOCK_TIMESTAMP, + "requirements": { + "production-requirements.lock": production_hash, + "build-tools.lock": build_tools_hash, + }, + "baseImages": [ + { + **base, + "platform": "linux/amd64", + "childManifestDigest": base["reference"].split("@", 1)[1], + } + for base in BASE_IMAGES + ], + } + + +def _canonical_json(value: Any) -> bytes: + return (json.dumps(value, sort_keys=True, indent=2) + "\n").encode("utf-8") + + +def _write_atomic(path: Path, content: bytes) -> None: + temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp") + try: + temporary.write_bytes(content) + os.replace(temporary, path) + finally: + temporary.unlink(missing_ok=True) + + +def _read_lock_manifest() -> dict[str, Any]: + try: + payload = json.loads(_require_file(LOCK_MANIFEST).decode("utf-8")) + except (json.JSONDecodeError, UnicodeDecodeError) as exc: + raise CandidateRecipeError("candidate lock manifest is invalid JSON") from exc + expected = _make_manifest( + source_hashes=SOURCE_HASHES, + production_hash=str( + payload.get("requirements", {}).get("production-requirements.lock", "") + ), + build_tools_hash=str(payload.get("requirements", {}).get("build-tools.lock", "")), + ) + if payload != expected: + raise CandidateRecipeError("candidate lock manifest differs from the reviewed contract") + for name, expected_hash in payload["requirements"].items(): + if sha256_hex(_require_file(LOCK_DIR / name)) != expected_hash: + raise CandidateRecipeError(f"candidate lock hash mismatch: {name}") + return payload + + +def verify_lock_contents(manifest: dict[str, Any]) -> None: + production = _require_file(LOCK_DIR / "production-requirements.lock").decode("utf-8") + build_tools = _require_file(LOCK_DIR / "build-tools.lock").decode("utf-8") + for requirement in CONSTRAINTS: + name, version = requirement.split("==", 1) + match = re.search(rf"(?im)^{re.escape(name)}==([^ ;\\]+)", production) + if match is None or match.group(1) != version: + raise CandidateRecipeError(f"production lock does not pin {name}=={version}") + for requirement in BUILD_TOOLS: + name, version = requirement.split("==", 1) + match = re.search(rf"(?im)^{re.escape(name)}==([^ ;\\]+)", build_tools) + if match is None or match.group(1) != version: + raise CandidateRecipeError(f"build-tool lock does not pin {name}=={version}") + for name, requirements in ( + ("production", production), + ("build-tool", build_tools), + ): + blocks = re.split(r"(?m)(?=^[A-Za-z0-9_.-]+==)", requirements) + blocks = [block for block in blocks if re.match(r"(?m)^[A-Za-z0-9_.-]+==", block)] + if not blocks or any( + not re.search(r"(?m)^\s*--hash=sha256:[0-9a-f]{64}\s*$", block) for block in blocks + ): + raise CandidateRecipeError(f"candidate {name} requirements must pin every wheel hash") + if "pip==26.2.1" not in build_tools: + raise CandidateRecipeError("candidate build tools must include the removable pip bootstrap") + if manifest["platform"] != "linux/amd64": + raise CandidateRecipeError("candidate lock platform differs from the fixed build platform") + + +def freeze_os_layers(text: str) -> str: + """Replace the four upstream RPM transactions with frozen-base assertions.""" + guards = ( + ( + 'RUN if [ "$ENABLE_RUST" = "true" ]; then', + 'RUN test "$ENABLE_RUST" = "false" && test "$ENABLE_RUST_MCP_RMCP" = "false"', + ), + ( + "RUN set -euo pipefail", + 'RUN test "$PYTHON_VERSION" = "3.12" && test -s /usr/share/itworx/rpm-inventory.txt', + ), + ( + "RUN microdnf upgrade", + 'RUN test "$PYTHON_VERSION" = "3.12" && test "$ENABLE_PROFILING" = "false" ' + '&& test "$(id -u app):$(id -g app)" = "10001:10001" ' + "&& test -s /usr/share/itworx/rpm-inventory.txt", + ), + ('RUN if [ "$ENABLE_FIPS" = "true" ]; then', 'RUN test "$ENABLE_FIPS" = "false"'), + ) + transactions = [ + match + for match in re.finditer(r"(?m)^RUN [^\n]*(?:\n(?<=\\\n)[^\n]*)*", text) + if re.search(r"\b(?:microdnf|dnf) (?:upgrade|install)\b", match.group()) + ] + if len(transactions) != len(guards): + raise CandidateRecipeError("upstream RPM transaction count changed") + for match, (prefix, guard) in reversed(list(zip(transactions, guards, strict=True))): + if not match.group().startswith(prefix): + raise CandidateRecipeError("upstream RPM transaction order changed") + text = text[: match.start()] + guard + text[match.end() :] + return text + + +def transform_containerfile(containerfile: bytes, expected_hash: str | None = None) -> bytes: + if sha256_hex(containerfile) != (expected_hash or SOURCE_HASHES["Containerfile"]): + raise CandidateRecipeError("pinned upstream Containerfile hash mismatch") + text = freeze_os_layers(containerfile.decode("utf-8")) + if text.count(LOCK_COPY_ANCHOR) != 1: + raise CandidateRecipeError("upstream dependency-copy anchor count changed") + text = text.replace( + LOCK_COPY_ANCHOR, + LOCK_COPY_ANCHOR + + "COPY contextforge-candidate-production-requirements.txt /tmp/contextforge-production-requirements.txt\n" + + "COPY contextforge-candidate-build-tools.txt /tmp/contextforge-build-tools.txt\n", + 1, + ) + + start = text.find(INSTALL_START) + end = text.find(INSTALL_END, start) + if start < 0 or end < 0 or text.find(INSTALL_START, start + 1) >= 0: + raise CandidateRecipeError("upstream dependency-install block anchors changed") + locked_install = """RUN set -euo pipefail \\ + && . /etc/profile.d/use-openssl.sh \\ + && python3 -m venv /app/.venv \\ + && /app/.venv/bin/python -m ensurepip --upgrade \\ + && /app/.venv/bin/python -m pip install --disable-pip-version-check --no-cache-dir --require-hashes --only-binary=:all: -r /tmp/contextforge-build-tools.txt \\ + && /app/.venv/bin/python -m pip install --disable-pip-version-check --no-cache-dir --require-hashes --only-binary=:all: -r /tmp/contextforge-production-requirements.txt \\ + && /app/.venv/bin/python -m pip install --disable-pip-version-check --no-cache-dir --no-deps --no-build-isolation ".[redis,postgres,observability,granian,plugins,llmchat]" \\ + && /app/.venv/bin/python -m pip check \\ + && /app/.venv/bin/python -c "import importlib.metadata as md; assert md.version('mcp') == '1.30.0'; assert md.version('PyJWT') == '2.15.0'; assert md.version('urllib3') == '2.8.0'" \\ + && /app/.venv/bin/python -m pip uninstall --yes pip setuptools wheel \\ + && (find /app/.venv -name "*.dist-info" -type d \\ + \\( -name "pip-*" -o -name "setuptools-*" -o -name "wheel-*" \\) \\ + -exec rm -rf {} + 2>/dev/null || true) \\ + && rm -rf /app/.venv/share/python-wheels \\ + && rm -rf "/app/"*.egg-info /app/build /app/dist /app/.eggs \\ + && rm -rf /root/.cache /var/cache/dnf +""" + text = text[:start] + locked_install + text[end:] + return text.encode("utf-8") + + +def prepare_source(source_dir: Path) -> None: + source = verify_upstream_source(source_dir) + manifest = _read_lock_manifest() + verify_lock_contents(manifest) + transformed = transform_containerfile(source["Containerfile"]) + + for source_name, target_name in LOCK_OUTPUT_NAMES.items(): + _write_atomic(source_dir / target_name, _require_file(LOCK_DIR / source_name)) + _write_atomic(source_dir / "Containerfile.itworx-candidate", transformed) + + +def print_build_args() -> None: + for key, value in candidate_build_args().items(): + print(f"{key}={value}") + + +def candidate_build_args() -> dict[str, str]: + manifest = _read_lock_manifest() + verify_lock_contents(manifest) + overrides = {item["arg"]: item["reference"] for item in BASE_IMAGES} + overrides.update( + { + "WHEELS_REF": overrides["UBI_MINIMAL"], + "PYTHON_VERSION": "3.12", + "ENABLE_RUST": "false", + "ENABLE_RUST_MCP_RMCP": "false", + "ENABLE_PROFILING": "false", + "ENABLE_FIPS": "false", + } + ) + return overrides + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + commands = parser.add_subparsers(dest="command", required=True) + prepare = commands.add_parser( + "prepare", help="verify and transform an exact fetched source tree" + ) + prepare.add_argument("--source-dir", type=Path, required=True) + generate = commands.add_parser("generate-locks", help="regenerate lock files with pinned uv") + generate.add_argument("--source-dir", type=Path, required=True) + generate.add_argument("--output-dir", type=Path, default=LOCK_DIR) + commands.add_parser("build-args", help="print fixed platform/base image build arguments") + args = parser.parse_args() + + try: + if args.command == "prepare": + prepare_source(args.source_dir) + elif args.command == "generate-locks": + generate_locks(args.source_dir, args.output_dir) + else: + print_build_args() + except (CandidateRecipeError, OSError, subprocess.SubprocessError) as exc: + parser.exit(1, f"FATAL: {exc}\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/draft_cve_exceptions.py b/scripts/draft_cve_exceptions.py index 93b77d8..47ffedf 100644 --- a/scripts/draft_cve_exceptions.py +++ b/scripts/draft_cve_exceptions.py @@ -16,6 +16,7 @@ satisfying the release gate for that finding. from __future__ import annotations import argparse +import hashlib import json import sys from datetime import date, timedelta @@ -28,6 +29,52 @@ if str(_ROOT_DIR) not in sys.path: from scripts.packlib import brussels_now, load_json # noqa: E402 +def _affected_image_digests(row: dict[str, object], images: dict[str, object]) -> dict[str, str]: + """Select current image bindings by scan tag, with a legacy full-inventory fallback.""" + if not images: + raise ValueError("--image-digests must contain a non-empty images map") + + if "affectedImages" not in row: + services = list(images) + else: + affected_images = row.get("affectedImages") + if ( + not isinstance(affected_images, list) + or not affected_images + or any(not isinstance(tag, str) or not tag.strip() for tag in affected_images) + or len(set(affected_images)) != len(affected_images) + ): + raise ValueError("scan affectedImages must be a non-empty list of unique image tags") + + services_by_tag: dict[str, list[str]] = {} + for service, entry in images.items(): + if not isinstance(entry, dict): + continue + tag = entry.get("tag") + if isinstance(tag, str) and tag: + services_by_tag.setdefault(tag, []).append(service) + + services = [] + for tag in affected_images: + matches = services_by_tag.get(tag, []) + if len(matches) != 1: + raise ValueError( + f"scan image {tag} must resolve to exactly one current image service" + ) + services.append(matches[0]) + + bindings: dict[str, str] = {} + for service in services: + entry = images.get(service) + if not isinstance(entry, dict): + raise ValueError(f"image evidence for {service} must be an object") + digest = entry.get("digest") or entry.get("imageId") + if not isinstance(digest, str) or not digest: + raise ValueError(f"image evidence for {service} has no immutable digest or imageId") + bindings[service] = digest + return bindings + + def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--scan-summary", required=True, type=Path) @@ -46,25 +93,41 @@ def main() -> int: scan_date = scan.get("scannedAt", brussels_now())[:10] scanner = f"trivy {scan.get('trivyVersion', 'unknown')}" - affected_digests: dict[str, str] = {} + image_inventory: dict[str, object] = {} if args.image_digests: digest_doc = load_json(args.image_digests) - for service, entry in digest_doc.get("images", {}).items(): - digest = entry.get("digest") or entry.get("imageId") - if digest: - affected_digests[service] = digest + raw_images = digest_doc.get("images", {}) + if isinstance(raw_images, dict): + image_inventory = raw_images today = date.fromisoformat(scan_date) if len(scan_date) == 10 else date.today() expiry = (today + timedelta(days=args.expiry_days)).isoformat() rescan = (today + timedelta(days=args.rescan_days)).isoformat() + versions_by_pair: dict[tuple[str, str], set[str]] = {} + for row in scan.get("unresolvedCriticalOrHigh", []): + for cve in row["cves"]: + versions_by_pair.setdefault((row["package"], cve), set()).add( + str(row.get("installedVersion")) + ) exceptions = [] for row in scan.get("unresolvedCriticalOrHigh", []): package = row["package"] + try: + affected_digests = ( + _affected_image_digests(row, image_inventory) if args.image_digests else {} + ) + except ValueError as exc: + parser.error(str(exc)) for cve in row["cves"]: + version_suffix = "" + if len(versions_by_pair[(package, cve)]) > 1: + version_suffix = ( + "-" + hashlib.sha256(str(row.get("installedVersion")).encode()).hexdigest()[:12] + ) exceptions.append( { - "id": f"{package}-{cve}", + "id": f"{package}-{cve}{version_suffix}", "cve": cve, "package": package, "installedVersion": row.get("installedVersion"), diff --git a/tests/fixtures/wp200-compose-config.env b/tests/fixtures/wp200-compose-config.env index b22241c..c600961 100644 --- a/tests/fixtures/wp200-compose-config.env +++ b/tests/fixtures/wp200-compose-config.env @@ -22,3 +22,6 @@ INSPECTOR_BIND_IP=127.0.0.1 COMPOSE_PROFILES=observability DEMO_TOKEN_REVOCATIONS_DIR_HOST=/tmp APPOPS_BUILD_DATE=2026-10-01T18:00:00Z +APPOPS_SOURCE_TREE=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +APPOPS_BUILD_ID=aaaaaaaaaaaa +SOURCE_DATE_EPOCH=1790888863 diff --git a/tests/test_compose.py b/tests/test_compose.py index d323e61..8734f12 100644 --- a/tests/test_compose.py +++ b/tests/test_compose.py @@ -259,16 +259,43 @@ class ComposeContractTests(unittest.TestCase): arguments.get("BUILD_DATE"), "${APPOPS_BUILD_DATE:?APPOPS_BUILD_DATE is required}", ) + for argument, value in { + "GIT_SOURCE_TREE": "${APPOPS_SOURCE_TREE:?APPOPS_SOURCE_TREE is required}", + "IMAGE_VERSION": "${APPOPS_BUILD_ID:?APPOPS_BUILD_ID is required}", + "SOURCE_DATE_EPOCH": "${SOURCE_DATE_EPOCH:?SOURCE_DATE_EPOCH is required}", + }.items(): + self.assertEqual(arguments.get(argument), value, name) original_arguments = self.document["services"][name]["build"].get("args", {}) for key, value in original_arguments.items(): self.assertEqual(arguments.get(key), value) + def test_build_dockerfiles_emit_controller_tree_version_and_epoch_labels(self) -> None: + dockerfiles = ( + "infrastructure/images/python-service/Dockerfile", + "infrastructure/images/docker-read-broker/Dockerfile", + "infrastructure/images/observability/Dockerfile", + ) + expected_labels = { + "org.opencontainers.image.source-tree": "GIT_SOURCE_TREE", + "org.opencontainers.image.version": "IMAGE_VERSION", + "org.opencontainers.image.source-date-epoch": "SOURCE_DATE_EPOCH", + } + for relative_path in dockerfiles: + text = (ROOT / relative_path).read_text(encoding="utf-8") + with self.subTest(dockerfile=relative_path): + for label, argument in expected_labels.items(): + self.assertIn(f"ARG {argument}", text) + self.assertIn(f'{label}="${{{argument}}}"', text) + def test_real_autodeploy_preserves_intake_digest_and_owned_build_revision(self) -> None: docker = shutil.which("docker") if docker is None: self.skipTest("docker CLI is not available in this environment") revision = "a" * 40 built_at = "2026-10-01T18:00:00Z" + source_tree = "b" * 40 + image_version = revision[:12] + source_date_epoch = "1790888863" with tempfile.TemporaryDirectory() as secrets_dir: for name in self.document["secrets"]: (Path(secrets_dir) / name).write_text("test-fixture-secret\n", encoding="utf-8") @@ -301,6 +328,9 @@ class ComposeContractTests(unittest.TestCase): "CONNECTOR_INGESTION_IMAGE": f"registry.example.test/intake@sha256:{'b' * 64}", "GITEA_COMMIT_SHA": revision, "APPOPS_BUILD_DATE": built_at, + "APPOPS_SOURCE_TREE": source_tree, + "APPOPS_BUILD_ID": image_version, + "SOURCE_DATE_EPOCH": source_date_epoch, }, ) self.assertEqual(completed.returncode, 0, completed.stderr) @@ -316,6 +346,9 @@ class ComposeContractTests(unittest.TestCase): continue self.assertEqual(service["build"]["args"]["GIT_REVISION"], revision, name) self.assertEqual(service["build"]["args"]["BUILD_DATE"], built_at, name) + self.assertEqual(service["build"]["args"]["GIT_SOURCE_TREE"], source_tree, name) + self.assertEqual(service["build"]["args"]["IMAGE_VERSION"], image_version, name) + self.assertEqual(service["build"]["args"]["SOURCE_DATE_EPOCH"], source_date_epoch, name) self.assertTrue(service["image"].endswith(f":{revision}"), name) def test_unraid_folder_view_groups_the_complete_production_stack(self) -> None: diff --git a/tests/test_contextforge_candidate_recipe.py b/tests/test_contextforge_candidate_recipe.py new file mode 100644 index 0000000..e8ced33 --- /dev/null +++ b/tests/test_contextforge_candidate_recipe.py @@ -0,0 +1,386 @@ +# The upstream TOML fixture retains exact source formatting. +# ruff: noqa: E501 + +from __future__ import annotations + +import hashlib +import json +import re +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "scripts")) + +import contextforge_candidate_recipe as recipe # noqa: E402 - script path added above + +LOCK_DIR = ROOT / "infrastructure" / "images" / "contextforge" / "candidate" + + +def _hash(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def _containerfile_fixture() -> bytes: + return ( + b"FROM base\n" + b'RUN if [ "$ENABLE_RUST" = "true" ]; then \\\n dnf upgrade -y; fi\n' + b"RUN set -euo pipefail \\\n && dnf install -y python3.12\n" + b"RUN microdnf upgrade -y \\\n && microdnf install -y python3.12\n" + b'RUN if [ "$ENABLE_FIPS" = "true" ]; then \\\n microdnf install -y crypto-policies; fi\n' + b"COPY pyproject.toml /app/\n" + + recipe.INSTALL_START.encode("utf-8") + + b"\n && /app/.venv/bin/uv pip install unpinned\n" + + recipe.INSTALL_END.encode("utf-8") + + b"COPY source /app\n" + ) + + +def _source_fixture(tmp_path: Path, containerfile: bytes) -> tuple[Path, dict[str, str]]: + source = tmp_path / "upstream" + source.mkdir() + contents = { + "Containerfile": containerfile, + "pyproject.toml": b"[project]\nname='fixture'\n", + "uv.lock": b"version = 1\n", + } + expected = {name: _hash(value) for name, value in contents.items()} + for name, value in contents.items(): + (source / name).write_bytes(value) + return source, expected + + +def _copy_candidate_locks(destination: Path) -> dict[str, str]: + destination.mkdir(parents=True) + requirements: dict[str, str] = {} + for name in ("production-requirements.lock", "build-tools.lock"): + data = (LOCK_DIR / name).read_bytes() + (destination / name).write_bytes(data) + requirements[name] = _hash(data) + return requirements + + +def test_candidate_lock_exports_are_hash_complete_and_exclude_build_tools() -> None: + manifest = json.loads((LOCK_DIR / "locks.json").read_text(encoding="utf-8")) + production = (LOCK_DIR / "production-requirements.lock").read_text(encoding="utf-8") + build_tools = (LOCK_DIR / "build-tools.lock").read_text(encoding="utf-8") + + assert manifest["upstreamCommit"] == recipe.UPSTREAM_COMMIT + assert manifest["uvVersion"] == recipe.UV_VERSION == "0.12.10" + assert manifest["globalExcludeNewerUtc"] == "2026-09-22T00:00:00Z" + assert manifest["freshnessOverrideUtc"] == "2026-10-02T00:00:00Z" + assert manifest["extras"] == list(recipe.EXTRAS) + assert manifest["constraints"] == list(recipe.CONSTRAINTS) + for name in ("production-requirements.lock", "build-tools.lock"): + assert _hash((LOCK_DIR / name).read_bytes()) == manifest["requirements"][name] + + for requirement in recipe.CONSTRAINTS: + package, version = requirement.split("==", 1) + assert re.search(rf"(?im)^{re.escape(package)}=={re.escape(version)}\s*\\", production) + for requirement in recipe.BUILD_TOOLS: + package, version = requirement.split("==", 1) + assert re.search(rf"(?im)^{re.escape(package)}=={re.escape(version)}\s*\\", build_tools) + + package_blocks = re.split(r"(?m)(?=^[A-Za-z0-9_.-]+==)", production) + package_blocks = [ + block for block in package_blocks if re.match(r"(?m)^[A-Za-z0-9_.-]+==", block) + ] + assert len(package_blocks) > 100 + assert all(re.search(r"--hash=sha256:[0-9a-f]{64}", block) for block in package_blocks) + assert not re.search(r"(?im)^(pip|setuptools|wheel|uv)==", production) + build_blocks = re.split(r"(?m)(?=^[A-Za-z0-9_.-]+==)", build_tools) + build_blocks = [block for block in build_blocks if re.match(r"(?m)^[A-Za-z0-9_.-]+==", block)] + assert all(re.search(r"--hash=sha256:[0-9a-f]{64}", block) for block in build_blocks) + assert "pip==26.2.1" in build_tools + + +def test_candidate_pyproject_freezes_relative_freshness_and_preserves_project_metadata() -> None: + source = """[tool.uv] +exclude-newer = "10 days" + +[tool.uv.exclude-newer-package] +known-safe-package = "2026-07-01T00:00:00Z" + +[tool.uv.sources] +compliance-reference-server = { path = "mcp-servers/python/compliance_reference_server", editable = true } + +# Project table follows the local source table. +[project] +name = "contextforge" +dependencies = ["httpx>=0.27"] + +[dependency-groups] +dev = ["pytest>=8"] +""" + + generated = recipe.candidate_pyproject(source) + import tomllib + + parsed = tomllib.loads(generated) + assert parsed["project"] == { + "name": "contextforge", + "dependencies": ["httpx>=0.27"], + } + assert "sources" not in parsed["tool"]["uv"] + assert parsed["tool"]["uv"]["exclude-newer"] == "2026-09-22T00:00:00Z" + assert parsed["tool"]["uv"]["constraint-dependencies"] == list(recipe.CONSTRAINTS) + assert parsed["tool"]["uv"]["exclude-newer-package"]["known-safe-package"] == ( + "2026-07-01T00:00:00Z" + ) + assert parsed["tool"]["uv"]["exclude-newer-package"]["pyjwt"] == "2026-10-02T00:00:00Z" + assert parsed["dependency-groups"]["build-tools"] == list(recipe.BUILD_TOOLS) + assert "dev" not in parsed["dependency-groups"] + + +def test_candidate_transform_is_exact_and_uses_hash_locked_runtime_install() -> None: + original = _containerfile_fixture() + transformed = recipe.transform_containerfile(original, expected_hash=_hash(original)).decode() + + assert transformed.count("COPY contextforge-candidate-production-requirements.txt") == 1 + assert transformed.count("COPY contextforge-candidate-build-tools.txt") == 1 + assert ( + "--require-hashes --only-binary=:all: -r /tmp/contextforge-production-requirements.txt" + in transformed + ) + assert ( + "--require-hashes --only-binary=:all: -r /tmp/contextforge-build-tools.txt" in transformed + ) + assert ( + '--no-deps --no-build-isolation ".[redis,postgres,observability,granian,plugins,llmchat]"' + in transformed + ) + assert "uv pip install unpinned" not in transformed + assert "COPY source /app" in transformed + assert not re.search(r"\b(?:dnf|microdnf) (?:upgrade|install)\b", transformed) + assert 'test "$ENABLE_RUST" = "false"' in transformed + assert 'test "$ENABLE_FIPS" = "false"' in transformed + assert 'test "$ENABLE_PROFILING" = "false"' in transformed + assert 'test "$(id -u app):$(id -g app)" = "10001:10001"' in transformed + + +def test_candidate_os_freeze_rejects_missing_or_additional_transactions() -> None: + original = _containerfile_fixture().decode() + for changed in ( + original.replace("dnf upgrade -y", "echo changed", 1), + original + "\nRUN dnf install -y unexpected\n", + ): + with pytest.raises(recipe.CandidateRecipeError, match="RPM transaction count"): + recipe.freeze_os_layers(changed) + + +def test_candidate_transform_rejects_source_drift_and_unexpected_anchor_counts() -> None: + original = _containerfile_fixture() + digest = _hash(original) + with pytest.raises(recipe.CandidateRecipeError, match="Containerfile hash mismatch"): + recipe.transform_containerfile(original + b"# drift\n", expected_hash=digest) + with pytest.raises(recipe.CandidateRecipeError, match="anchor count changed"): + recipe.transform_containerfile( + original.replace(b"COPY pyproject.toml /app/\n", b""), + expected_hash=_hash(original.replace(b"COPY pyproject.toml /app/\n", b"")), + ) + + +def test_source_verification_rejects_missing_and_tampered_pinned_inputs(tmp_path: Path) -> None: + source, expected = _source_fixture(tmp_path, _containerfile_fixture()) + assert recipe.verify_upstream_source(source, expected) == { + name: (source / name).read_bytes() for name in expected + } + (source / "uv.lock").write_bytes(b"changed lock") + with pytest.raises(recipe.CandidateRecipeError, match="uv.lock hash mismatch"): + recipe.verify_upstream_source(source, expected) + (source / "uv.lock").unlink() + with pytest.raises(recipe.CandidateRecipeError, match="missing: uv.lock"): + recipe.verify_upstream_source(source, expected) + + +def test_prepare_copies_verified_locks_and_fails_before_writing_on_missing_lock( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + source, source_hashes = _source_fixture(tmp_path, _containerfile_fixture()) + lock_dir = tmp_path / "candidate-locks" + lock_hashes = _copy_candidate_locks(lock_dir) + manifest = recipe._make_manifest( + source_hashes=source_hashes, + production_hash=lock_hashes["production-requirements.lock"], + build_tools_hash=lock_hashes["build-tools.lock"], + ) + (lock_dir / "locks.json").write_bytes(recipe._canonical_json(manifest)) + monkeypatch.setattr(recipe, "SOURCE_HASHES", source_hashes) + monkeypatch.setattr(recipe, "LOCK_DIR", lock_dir) + monkeypatch.setattr(recipe, "LOCK_MANIFEST", lock_dir / "locks.json") + + recipe.prepare_source(source) + assert (source / "Containerfile.itworx-candidate").exists() + for name, target in recipe.LOCK_OUTPUT_NAMES.items(): + assert (source / target).read_bytes() == (lock_dir / name).read_bytes() + + (source / "Containerfile.itworx-candidate").unlink() + (lock_dir / "build-tools.lock").unlink() + with pytest.raises(recipe.CandidateRecipeError, match="missing: build-tools.lock"): + recipe.prepare_source(source) + assert not (source / "Containerfile.itworx-candidate").exists() + + +def test_candidate_build_args_pin_every_base_and_disable_optional_builds() -> None: + args = recipe.candidate_build_args() + assert set(args) == { + "UBI_BASE", + "NODEJS_IMAGE", + "UBI_MINIMAL", + "WHEELS_REF", + "PYTHON_VERSION", + "ENABLE_RUST", + "ENABLE_RUST_MCP_RMCP", + "ENABLE_PROFILING", + "ENABLE_FIPS", + } + assert all("@sha256:" in args[key] for key in ("UBI_BASE", "NODEJS_IMAGE", "UBI_MINIMAL")) + assert args["UBI_BASE"] == ( + "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder@" + "sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483" + ) + assert args["UBI_MINIMAL"] == ( + "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@" + "sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236" + ) + assert args["NODEJS_IMAGE"] == ( + "registry.access.redhat.com/ubi10/nodejs-24@" + "sha256:0f110e68682f4a49188ba4db786eb4f207b119420913fc76bb5ba9512ffbef06" + ) + assert args["WHEELS_REF"] == args["UBI_MINIMAL"] + assert args["PYTHON_VERSION"] == "3.12" + assert ( + args["ENABLE_RUST"] == args["ENABLE_RUST_MCP_RMCP"] == args["ENABLE_PROFILING"] == "false" + ) + assert args["ENABLE_FIPS"] == "false" + + +def test_frozen_base_manifest_binds_published_digests_and_rpm_inventories() -> None: + manifest = json.loads((LOCK_DIR / "locks.json").read_text(encoding="utf-8")) + bases = {item["arg"]: item for item in manifest["baseImages"]} + + assert set(bases) == {"UBI_BASE", "NODEJS_IMAGE", "UBI_MINIMAL"} + assert bases["UBI_BASE"] == { + "arg": "UBI_BASE", + "sourceTag": ( + "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder:" + "sha256-c6bb70a7a340ed1047e7cb934aa55b086ee68a5eb9ba6b2ad2e6825c7184a3e8" + ), + "reference": ( + "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder@" + "sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483" + ), + "parentReference": ( + "registry.access.redhat.com/ubi10@" + "sha256:1e32d829a4de713a835d702d3bc05f0c448a08590796281b7dec181912e9aeb8" + ), + "imageId": "sha256:c6bb70a7a340ed1047e7cb934aa55b086ee68a5eb9ba6b2ad2e6825c7184a3e8", + "rpmInventorySha256": "6a8c0d4f66fc5a38008541ad15614711e7f536cd18c70c02d2eb0691552b88b0", + "platform": "linux/amd64", + "childManifestDigest": ( + "sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483" + ), + } + assert bases["UBI_MINIMAL"] == { + "arg": "UBI_MINIMAL", + "sourceTag": ( + "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime:" + "sha256-fd94e2643dfc0401790d0528d3740bb2f0c04709e743f6c774a4e8a38606e40b" + ), + "reference": ( + "gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@" + "sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236" + ), + "parentReference": ( + "registry.access.redhat.com/ubi10/ubi-minimal@" + "sha256:afbfb5c18c628796fed3ad3122dcf096e0b2dec9dad3fbabe8620de06bc6f078" + ), + "imageId": "sha256:fd94e2643dfc0401790d0528d3740bb2f0c04709e743f6c774a4e8a38606e40b", + "rpmInventorySha256": "a74d60ecb43d1aa561874305d1a9c62ac35c99a14c1dc275ba1656f14df7d034", + "platform": "linux/amd64", + "childManifestDigest": ( + "sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236" + ), + } + assert bases["NODEJS_IMAGE"]["reference"] == ( + "registry.access.redhat.com/ubi10/nodejs-24@" + "sha256:0f110e68682f4a49188ba4db786eb4f207b119420913fc76bb5ba9512ffbef06" + ) + assert "imageId" not in bases["NODEJS_IMAGE"] + + +def test_frozen_base_reference_drift_fails_before_candidate_build( + monkeypatch: pytest.MonkeyPatch, +) -> None: + original = recipe.BASE_IMAGES[0] + changed = dict(original) + changed["reference"] = changed["reference"].replace("e23a733", "f23a733", 1) + monkeypatch.setattr(recipe, "BASE_IMAGES", (changed, *recipe.BASE_IMAGES[1:])) + + with pytest.raises(recipe.CandidateRecipeError, match="reviewed contract"): + recipe._read_lock_manifest() + + +def test_frozen_base_evidence_matches_candidate_pins_and_repository_inputs() -> None: + evidence_path = ROOT / "evidence" / "WP-400" / "CF_FROZEN_BASES.json" + evidence = json.loads(evidence_path.read_text(encoding="utf-8")) + manifest = json.loads((LOCK_DIR / "locks.json").read_text(encoding="utf-8")) + bases = {item["arg"]: item for item in manifest["baseImages"]} + + assert evidence["schema"] == "itworx.contextforge-frozen-os-bases-evidence/v1" + assert evidence["status"] == "passed" + assert evidence["finalContextForgeCandidateBuilt"] is False + assert evidence["sourceInputs"]["upstreamCommit"] == manifest["upstreamCommit"] + assert evidence["sourceInputs"]["ContainerfileSha256"] == manifest["upstreamFiles"][ + "Containerfile" + ] + assert evidence["sourceInputs"]["pyprojectTomlSha256"] == manifest["upstreamFiles"][ + "pyproject.toml" + ] + assert evidence["sourceInputs"]["uvLockSha256"] == manifest["upstreamFiles"]["uv.lock"] + assert set(evidence["hubInputsSha256"]) == { + "scripts/contextforge_candidate_recipe.py", + "scripts/build_contextforge_compat_image.sh", + "infrastructure/images/contextforge/candidate/locks.json", + "infrastructure/images/contextforge/candidate/production-requirements.lock", + "infrastructure/images/contextforge/candidate/build-tools.lock", + "infrastructure/images/contextforge/os-bases/Dockerfile.builder", + "infrastructure/images/contextforge/os-bases/Dockerfile.runtime", + "infrastructure/compose/docker-compose.autodeploy.yml", + } + assert evidence["hubInputsSha256"] == { + name: _hash((ROOT / name).read_bytes()) for name in evidence["hubInputsSha256"] + } + for role, arg in (("builder", "UBI_BASE"), ("runtime", "UBI_MINIMAL")): + published = evidence["publishedBases"][role] + locked = bases[arg] + assert published["reference"] == locked["reference"] + assert published["imageId"] == locked["imageId"] + assert published["rpmInventorySha256"] == locked["rpmInventorySha256"] + assert published["platform"] == locked["platform"] == "linux/amd64" + assert published["publication"]["pulledByDigest"] is True + assert published["publication"]["manifestConfigImageIdMatches"] is True + assert published["publication"]["rpmInventoryMatchesAfterPull"] is True + assert published["rpmInventory"]["exactMatch"] is True + assert published["scan"]["secretFindings"] == 0 + assert published["scan"]["vulnerabilityFindingRecords"] == 0 + + +def test_candidate_manifest_rejects_changed_lock_bytes( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + lock_dir = tmp_path / "locks" + lock_hashes = _copy_candidate_locks(lock_dir) + manifest = recipe._make_manifest( + source_hashes=recipe.SOURCE_HASHES, + production_hash=lock_hashes["production-requirements.lock"], + build_tools_hash=lock_hashes["build-tools.lock"], + ) + (lock_dir / "locks.json").write_bytes(recipe._canonical_json(manifest)) + (lock_dir / "production-requirements.lock").write_bytes(b"changed") + monkeypatch.setattr(recipe, "LOCK_DIR", lock_dir) + monkeypatch.setattr(recipe, "LOCK_MANIFEST", lock_dir / "locks.json") + with pytest.raises(recipe.CandidateRecipeError, match="lock hash mismatch"): + recipe._read_lock_manifest() diff --git a/tests/test_contextforge_compat_image_contract.py b/tests/test_contextforge_compat_image_contract.py index 0976f47..55ec6a8 100644 --- a/tests/test_contextforge_compat_image_contract.py +++ b/tests/test_contextforge_compat_image_contract.py @@ -1,9 +1,15 @@ +# Shell fixture lines preserve exact mocked build arguments and OCI values. +# ruff: noqa: E501 + from __future__ import annotations +import hashlib import json import os +import re import shutil import subprocess +import tomllib from pathlib import Path import pytest @@ -12,6 +18,9 @@ ROOT = Path(__file__).resolve().parents[1] MANIFEST = ROOT / "infrastructure" / "images" / "contextforge" / "compat-image.env" CONTRACT = ROOT / "infrastructure" / "images" / "contextforge" / "runtime-contract.json" BUILDER = ROOT / "scripts" / "build_contextforge_compat_image.sh" +SECURITY_REQUIREMENTS = ( + ROOT / "infrastructure" / "images" / "contextforge" / "security-requirements.txt" +) EXPECTED = { "CONTEXTFORGE_UPSTREAM_COMMIT": "1e58a0434a78bbdbe1e975abeb0bed1043ff14c1", @@ -73,18 +82,18 @@ def test_contextforge_compat_builder_guards_source_and_runtime() -> None: assert 'CONTRACT="$ROOT/infrastructure/images/contextforge/runtime-contract.json"' in script assert 'ENTRYPOINT="$ROOT/infrastructure/images/contextforge/entrypoint.sh"' in script assert 'RUNTIME_VERIFIER="$ROOT/scripts/contextforge_runtime_contract.py"' in script - assert 'https://github.com/IBM/mcp-context-forge.git' in script + assert "https://github.com/IBM/mcp-context-forge.git" in script assert 'git -C "$SRC" fetch --depth 1 origin "$CONTEXTFORGE_UPSTREAM_COMMIT"' in script assert 'git -C "$SRC" checkout --detach FETCH_HEAD' in script assert 'git -C "$SRC" rev-parse HEAD' in script assert 'REVISION_PREFIX="${CONTEXTFORGE_ALEMBIC_HEAD}_"' in script assert 'MCP_REQUIREMENT="mcp==${CONTEXTFORGE_MCP_SDK}"' in script assert "from mcp import McpError" in script - assert 'alembic -c mcpgateway/alembic.ini heads' in script - assert 'CONTEXTFORGE_ALEMBIC_HEAD' in script - assert 'contextforge-runtime-contract.json' in script - assert 'contextforge-entrypoint.sh' in script - assert 'contextforge-runtime-contract.py' in script + assert "alembic -c mcpgateway/alembic.ini heads" in script + assert "CONTEXTFORGE_ALEMBIC_HEAD" in script + assert "contextforge-runtime-contract.json" in script + assert "contextforge-entrypoint.sh" in script + assert "contextforge-runtime-contract.py" in script for label in ( "io.itworx.contextforge.contract-sha256", "io.itworx.contextforge.entrypoint-sha256", @@ -92,12 +101,68 @@ def test_contextforge_compat_builder_guards_source_and_runtime() -> None: "io.itworx.contextforge.source-commit", "io.itworx.contextforge.alembic-head", "io.itworx.contextforge.mcp-sdk", + "io.itworx.contextforge.security-requirements-sha256", ): assert label in script - assert 'docker image inspect -f' in script + assert "docker image inspect -f" in script assert 'docker tag "$STAGED_IMAGE" "$CONTEXTFORGE_LOCAL_IMAGE"' in script +def test_contextforge_security_wheels_match_the_reviewed_hub_lock() -> None: + lock = tomllib.loads((ROOT / "uv.lock").read_text(encoding="utf-8")) + packages = {package["name"]: package for package in lock["package"]} + requirements = SECURITY_REQUIREMENTS.read_text(encoding="utf-8").splitlines() + assert len(requirements) == 2 + names = set() + for requirement in requirements: + match = re.fullmatch( + r"([A-Za-z0-9]+)==([0-9]+\.[0-9]+\.[0-9]+) --hash=sha256:([0-9a-f]{64})", requirement + ) + assert match is not None, ( + "Security inputs must pin a wheel version and SHA-256 without extra flags." + ) + name, version, digest = match.groups() + normalized = name.lower() + assert normalized not in names + names.add(normalized) + package = packages[normalized] + assert version == package["version"] + assert f"sha256:{digest}" in {wheel["hash"] for wheel in package["wheels"]} + assert names == {"pyjwt", "urllib3"} + + +def test_missing_security_inputs_stop_builder_before_any_external_command(tmp_path: Path) -> None: + if os.name == "nt": + pytest.skip("The Linux builder executes in the required Linux validation job") + scripts = tmp_path / "scripts" + scripts.mkdir() + builder = scripts / BUILDER.name + builder.write_text(BUILDER.read_text(encoding="utf-8"), encoding="utf-8") + inputs = tmp_path / "infrastructure" / "images" / "contextforge" + inputs.mkdir(parents=True) + for name in ("compat-image.env", "runtime-contract.json", "entrypoint.sh"): + (inputs / name).write_text("fixture", encoding="utf-8") + (scripts / "contextforge_runtime_contract.py").write_text("fixture", encoding="utf-8") + bin_directory = tmp_path / "bin" + bin_directory.mkdir() + marker = tmp_path / "external-command-ran" + for name in ("git", "docker"): + executable = bin_directory / name + executable.write_text(f"#!/bin/sh\ntouch '{marker}'\nexit 99\n", encoding="utf-8") + executable.chmod(0o755) + env = {**os.environ, "PATH": f"{bin_directory}:{os.environ['PATH']}"} + completed = subprocess.run( # noqa: S603 + [shutil.which("bash") or "/bin/bash", str(builder)], + env=env, + capture_output=True, + text=True, + check=False, + ) + assert completed.returncode == 66 + assert "security-requirements.txt" in completed.stderr + assert not marker.exists() + + def test_contextforge_compat_builder_has_valid_shell_syntax() -> None: if os.name == "nt": pytest.skip("WSL bash cannot read a Windows path for syntax validation") @@ -110,3 +175,392 @@ def test_contextforge_compat_builder_has_valid_shell_syntax() -> None: check=False, ) assert completed.returncode == 0, completed.stderr + + +def _candidate_builder_fixture(tmp_path: Path) -> tuple[Path, dict[str, str], Path, Path]: + root = tmp_path / "hub" + for relative in ( + "scripts/build_contextforge_compat_image.sh", + "scripts/contextforge_candidate_recipe.py", + "scripts/contextforge_runtime_contract.py", + "infrastructure/images/contextforge/compat-image.env", + "infrastructure/images/contextforge/runtime-contract.json", + "infrastructure/images/contextforge/entrypoint.sh", + "infrastructure/images/contextforge/security-requirements.txt", + ): + target = root / relative + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(ROOT / relative, target) + bin_dir = tmp_path / "bin" + bin_dir.mkdir() + log = tmp_path / "external-calls.log" + layer_capture = tmp_path / "candidate-layer.Containerfile" + git = bin_dir / "git" + git.write_text( + "#!/bin/sh\n" + 'echo "git $*" >> "$CALL_LOG"\n' + 'if [ "$1" = "-C" ]; then\n' + ' src="$2"; action="$3"\n' + ' if [ "$src" = "$SOURCE_ROOT" ]; then\n' + ' case "$action:$4:$5" in\n' + ' rev-parse:HEAD:) printf "%s\\n" "$GIT_HEAD" ;;\n' + ' rev-parse:HEAD*:) printf "%s\\n" "$GIT_TREE" ;;\n' + ' show:-s:--format=%ct) printf "%s\\n" "$GIT_EPOCH" ;;\n' + ' show:-s:--format=%cI) printf "%s\\n" "$GIT_COMMIT_DATE" ;;\n' + ' status:*:*) printf "%s" "${GIT_DIRTY:-}" ;;\n' + " esac\n" + " exit 0\n" + " fi\n" + ' case "$action" in\n' + ' fetch|checkout) mkdir -p "$src/mcpgateway/alembic/versions"; ' + ' printf "revision = \\"12d4a0c7789c\\"\\n" > ' + '"$src/mcpgateway/alembic/versions/12d4a0c7789c_fixture.py"; ' + ' printf "FROM python:3.12\\n" > "$src/Containerfile" ;;\n' + ' rev-parse) printf "1e58a0434a78bbdbe1e975abeb0bed1043ff14c1\\n" ;;\n' + " esac\n" + "fi\n" + "exit 0\n", + encoding="utf-8", + ) + git.chmod(0o755) + real_python = shutil.which("python3") + assert real_python is not None + python3 = bin_dir / "python3" + python3.write_text( + "#!/bin/sh\n" + 'case "$1" in\n' + " */contextforge_candidate_recipe.py)\n" + ' case "$2" in\n' + ' prepare) printf "candidate recipe\\n" > "$4/Containerfile.itworx-candidate" ;;\n' + " build-args)\n" + ' printf "%s\\n" \\\n' + ' "UBI_BASE=gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder@sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483" \\\n' + ' "NODEJS_IMAGE=registry.access.redhat.com/ubi10/nodejs-24@sha256:0f110e68682f4a49188ba4db786eb4f207b119420913fc76bb5ba9512ffbef06" \\\n' + ' "UBI_MINIMAL=gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236" \\\n' + ' "WHEELS_REF=gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236" \\\n' + ' "PYTHON_VERSION=3.12" "ENABLE_RUST=false" "ENABLE_RUST_MCP_RMCP=false" "ENABLE_PROFILING=false" "ENABLE_FIPS=false" ;;\n' + " esac\n" + " exit 0 ;;\n" + "esac\n" + f'exec "{real_python}" "$@"\n', + encoding="utf-8", + ) + python3.chmod(0o755) + docker = bin_dir / "docker" + docker.write_text( + "#!/bin/sh\n" + 'echo "docker $*" >> "$CALL_LOG"\n' + 'if [ "$1" = "image" ] && [ "$2" = "inspect" ]; then\n' + ' if [ "$3" != "-f" ]; then\n' + ' [ "${COLLISION_TAG:-}" = "$3" ] && exit 0\n' + ' printf "Error response from daemon: No such image: %s\\n" "$3" >&2\n' + " exit 1\n" + " fi\n" + ' case "$4" in\n' + ' *Config.User*) printf "10001:10001\\n" ;;\n' + ' *contract-sha256*) printf "%s\\n" "$CONTRACT_SHA" ;;\n' + ' *entrypoint-sha256*) printf "%s\\n" "$ENTRYPOINT_SHA" ;;\n' + ' *verifier-sha256*) printf "%s\\n" "$VERIFIER_SHA" ;;\n' + ' *source-commit*) printf "1e58a0434a78bbdbe1e975abeb0bed1043ff14c1\\n" ;;\n' + ' *alembic-head*) printf "12d4a0c7789c\\n" ;;\n' + ' *mcp-sdk*) printf "1.30.0\\n" ;;\n' + ' *security-requirements-sha256*) printf "%s\\n" "$SECURITY_SHA" ;;\n' + ' *org.opencontainers.image.source-tree*) printf "%s\\n" "$APPOPS_SOURCE_TREE" ;;\n' + ' *org.opencontainers.image.revision*) printf "%s\\n" "$APPOPS_SOURCE_REVISION" ;;\n' + ' *org.opencontainers.image.version*) printf "%s\\n" "$APPOPS_BUILD_ID" ;;\n' + ' *org.opencontainers.image.source-date-epoch*) printf "%s\\n" "$SOURCE_DATE_EPOCH" ;;\n' + ' *org.opencontainers.image.created*) printf "%s\\n" "$APPOPS_BUILD_DATE" ;;\n' + ' *org.opencontainers.image.source*) printf "%s\\n" "$OCI_SOURCE" ;;\n' + " esac\n" + " exit 0\n" + "fi\n" + 'if [ "$1" = "build" ]; then\n' + ' while [ "$#" -gt 0 ]; do\n' + ' if [ "$1" = "-f" ]; then layer="$2"; shift 2; else shift; fi\n' + " done\n" + ' case "$layer" in *Containerfile.compat) cp "$layer" "$LAYER_CAPTURE" ;; esac\n' + " exit 0\n" + "fi\n" + 'if [ "$1" = "run" ]; then\n' + ' case "$*" in\n' + ' *"alembic -c mcpgateway/alembic.ini heads"*) printf "12d4a0c7789c (head)\\n" ;;\n' + ' *"contextforge-runtime-contract.json"*) printf "%s\\n" "$CONTRACT_SHA" ;;\n' + ' *"contextforge-entrypoint.sh"*) printf "%s\\n" "$ENTRYPOINT_SHA" ;;\n' + ' *"contextforge-runtime-contract.py"*) printf "%s\\n" "$VERIFIER_SHA" ;;\n' + ' *"contextforge-security-requirements.txt"*) printf "%s\\n" "$SECURITY_SHA" ;;\n' + " esac\n" + " exit 0\n" + "fi\n" + "exit 0\n", + encoding="utf-8", + ) + docker.chmod(0o755) + contract_sha = hashlib.sha256( + (root / "infrastructure/images/contextforge/runtime-contract.json").read_bytes() + ).hexdigest() + entrypoint_sha = hashlib.sha256( + (root / "infrastructure/images/contextforge/entrypoint.sh").read_bytes() + ).hexdigest() + verifier_sha = hashlib.sha256( + (root / "scripts/contextforge_runtime_contract.py").read_bytes() + ).hexdigest() + security_sha = hashlib.sha256( + (root / "infrastructure/images/contextforge/security-requirements.txt").read_bytes() + ).hexdigest() + environment = { + **os.environ, + "PATH": f"{bin_dir}:{os.environ['PATH']}", + "CALL_LOG": str(log), + "LAYER_CAPTURE": str(layer_capture), + "SOURCE_ROOT": str(root), + "CONTRACT_SHA": contract_sha, + "ENTRYPOINT_SHA": entrypoint_sha, + "VERIFIER_SHA": verifier_sha, + "SECURITY_SHA": security_sha, + "OCI_SOURCE": "https://gitea.example.com/jens/itworx-mcp-hub", + "GIT_HEAD": "a" * 40, + "GIT_TREE": "b" * 40, + "GIT_EPOCH": "1790964000", + "GIT_COMMIT_DATE": "2026-09-30T00:00:00+00:00", + "APPOPS_SOURCE_REPOSITORY": "jens/itworx_mcp_hub", + "APPOPS_SOURCE_REVISION": "a" * 40, + "APPOPS_SOURCE_TREE": "b" * 40, + "APPOPS_BUILD_ID": "a" * 12, + "APPOPS_BUILD_DATE": "2026-10-02T18:00:00.123Z", + "SOURCE_DATE_EPOCH": "1790964000", + "GITEA_REPOSITORY": "jens/itworx_mcp_hub", + "GITEA_COMMIT_SHA": "a" * 40, + } + return root / "scripts/build_contextforge_compat_image.sh", environment, log, layer_capture + + +def test_candidate_mode_requires_frozen_metadata_before_git_or_docker(tmp_path: Path) -> None: + if os.name == "nt": + pytest.skip("The Linux builder executes in the required Linux validation job") + builder, env, log, _layer = _candidate_builder_fixture(tmp_path) + env.pop("APPOPS_SOURCE_TREE") + completed = subprocess.run( # noqa: S603 + [shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"], + env=env, + capture_output=True, + text=True, + check=False, + ) + assert completed.returncode != 0 + assert "APPOPS_SOURCE_TREE" in completed.stderr + assert not log.exists() or log.read_text(encoding="utf-8") == "" + + +def test_candidate_mode_rejects_unreviewed_build_variants_before_git_or_docker( + tmp_path: Path, +) -> None: + if os.name == "nt": + pytest.skip("The Linux builder executes in the required Linux validation job") + builder, env, log, _layer = _candidate_builder_fixture(tmp_path) + completed = subprocess.run( # noqa: S603 + [ + shutil.which("bash") or "/bin/bash", + str(builder), + "--release-candidate", + "--enable-fips", + ], + env=env, + capture_output=True, + text=True, + check=False, + ) + assert completed.returncode == 64 + assert "supported mode" in completed.stderr + assert not log.exists() or log.read_text(encoding="utf-8") == "" + + +def test_candidate_mode_rejects_invalid_calendar_date_before_git_or_docker( + tmp_path: Path, +) -> None: + if os.name == "nt": + pytest.skip("The Linux builder executes in the required Linux validation job") + builder, env, log, _layer = _candidate_builder_fixture(tmp_path) + env["APPOPS_BUILD_DATE"] = "2026-02-30T18:00:00.123Z" + completed = subprocess.run( # noqa: S603 + [shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"], + env=env, + capture_output=True, + text=True, + check=False, + ) + assert completed.returncode != 0 + assert "valid UTC calendar timestamp" in completed.stderr + assert not log.exists() or log.read_text(encoding="utf-8") == "" + + +@pytest.mark.parametrize("collision", ["final", "upstream", "staged"]) +def test_candidate_mode_rejects_existing_revision_tags_after_source_validation_before_build( + tmp_path: Path, collision: str +) -> None: + if os.name == "nt": + pytest.skip("The Linux builder executes in the required Linux validation job") + builder, env, log, _layer = _candidate_builder_fixture(tmp_path) + revision = env["APPOPS_SOURCE_REVISION"] + suffix = {"final": "", "upstream": "-upstream", "staged": "-staged"}[collision] + env["COLLISION_TAG"] = f"itworx-mcp-hub/contextforge-schema:{revision}{suffix}" + completed = subprocess.run( # noqa: S603 + [shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"], + env=env, + capture_output=True, + text=True, + check=False, + ) + assert completed.returncode != 0 + assert "already exists" in completed.stderr + calls = log.read_text(encoding="utf-8").splitlines() + assert any("docker image inspect" in call for call in calls) + assert any(call.startswith("git ") for call in calls) + assert not any("docker build" in call or "docker tag" in call for call in calls) + + +def test_candidate_mode_uses_only_revision_scoped_tags_and_frozen_oci_metadata( + tmp_path: Path, +) -> None: + if os.name == "nt": + pytest.skip("The Linux builder executes in the required Linux validation job") + builder, env, log, layer_capture = _candidate_builder_fixture(tmp_path) + static = EXPECTED["CONTEXTFORGE_LOCAL_IMAGE"] + revision = env["APPOPS_SOURCE_REVISION"] + completed = subprocess.run( # noqa: S603 + [shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"], + env=env, + capture_output=True, + text=True, + check=False, + ) + assert completed.returncode == 0, completed.stderr + calls = log.read_text(encoding="utf-8").splitlines() + tags = [call for call in calls if call.startswith("docker tag ")] + assert tags == [ + f"docker tag itworx-mcp-hub/contextforge-schema:{revision}-staged " + f"itworx-mcp-hub/contextforge-schema:{revision}" + ] + assert any( + f"-t itworx-mcp-hub/contextforge-schema:{revision}-upstream " in call for call in calls + ) + assert any( + f"-t itworx-mcp-hub/contextforge-schema:{revision}-staged " in call for call in calls + ) + candidate_build = next( + call + for call in calls + if "docker build" in call and "Containerfile.itworx-candidate" in call + ) + assert "--platform linux/amd64" in candidate_build + assert ( + "UBI_BASE=gitea.example.com/jens/itworx-mcp-hub/contextforge-os-builder@" + "sha256:e23a733abd28376eb3328676a3ab1e6d153122228110de0c881bb5f3c38a4483" + in candidate_build + ) + assert ( + "NODEJS_IMAGE=registry.access.redhat.com/ubi10/nodejs-24@sha256:0f110e686" + in candidate_build + ) + assert ( + "UBI_MINIMAL=gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@" + "sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236" + in candidate_build + ) + assert ( + "WHEELS_REF=gitea.example.com/jens/itworx-mcp-hub/contextforge-os-runtime@" + "sha256:9b23d8152f253b10e47ce077bb9c5f8c3c370f3e0d5533f8a4a0e986ff176236" + in candidate_build + ) + assert "ENABLE_RUST=false" in candidate_build + assert "ENABLE_RUST_MCP_RMCP=false" in candidate_build + assert "ENABLE_PROFILING=false" in candidate_build + assert "ENABLE_FIPS=false" in candidate_build + assert not any(static in call for call in calls) + assert not any(call.startswith("docker rmi ") for call in calls) + assert "NOT APPROVED OR DEPLOYED" in completed.stdout + layer = layer_capture.read_text(encoding="utf-8") + assert "-m pip" not in layer + assert "import importlib.util" in layer + assert "find_spec" in layer + assert ( + 'org.opencontainers.image.source="https://gitea.example.com/jens/itworx-mcp-hub"' in layer + ) + assert f'org.opencontainers.image.revision="{revision}"' in layer + assert f'org.opencontainers.image.source-tree="{env["APPOPS_SOURCE_TREE"]}"' in layer + assert f'org.opencontainers.image.version="{env["APPOPS_BUILD_ID"]}"' in layer + assert f'org.opencontainers.image.created="{env["APPOPS_BUILD_DATE"]}"' in layer + assert f'org.opencontainers.image.source-date-epoch="{env["SOURCE_DATE_EPOCH"]}"' in layer + + +@pytest.mark.parametrize( + "dirty", [" M scripts/contextforge_runtime_contract.py\n", "?? unexpected.py\n"] +) +def test_candidate_mode_rejects_a_dirty_source_checkout_before_fetch_or_build( + tmp_path: Path, + dirty: str, +) -> None: + if os.name == "nt": + pytest.skip("The Linux builder executes in the required Linux validation job") + builder, env, log, _layer = _candidate_builder_fixture(tmp_path) + env["GIT_DIRTY"] = dirty + completed = subprocess.run( # noqa: S603 + [shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"], + env=env, + capture_output=True, + text=True, + check=False, + ) + assert completed.returncode != 0 + assert "source checkout has changes" in completed.stderr + calls = log.read_text(encoding="utf-8").splitlines() + assert not any("git init" in call or "git fetch" in call for call in calls) + assert not any(call.startswith("docker ") for call in calls) + assert not any("docker build" in call or "docker tag" in call for call in calls) + + +def test_candidate_mode_rejects_source_tree_mismatch_before_fetch_or_build( + tmp_path: Path, +) -> None: + if os.name == "nt": + pytest.skip("The Linux builder executes in the required Linux validation job") + builder, env, log, _layer = _candidate_builder_fixture(tmp_path) + env["APPOPS_SOURCE_TREE"] = "c" * 40 + completed = subprocess.run( # noqa: S603 + [shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"], + env=env, + capture_output=True, + text=True, + check=False, + ) + assert completed.returncode != 0 + assert "source tree differs" in completed.stderr + calls = log.read_text(encoding="utf-8").splitlines() + assert any("rev-parse" in call for call in calls) + assert not any("git init" in call or "git fetch" in call for call in calls) + assert not any(call.startswith("docker ") for call in calls) + assert not any("docker build" in call or "docker tag" in call for call in calls) + + +def test_candidate_mode_rejects_revision_mismatch_with_checked_out_head( + tmp_path: Path, +) -> None: + if os.name == "nt": + pytest.skip("The Linux builder executes in the required Linux validation job") + builder, env, log, _layer = _candidate_builder_fixture(tmp_path) + env["APPOPS_SOURCE_REVISION"] = "c" * 40 + env["GITEA_COMMIT_SHA"] = "c" * 40 + env["APPOPS_BUILD_ID"] = "c" * 12 + completed = subprocess.run( # noqa: S603 + [shutil.which("bash") or "/bin/bash", str(builder), "--release-candidate"], + env=env, + capture_output=True, + text=True, + check=False, + ) + assert completed.returncode != 0 + assert "checked-out Hub revision differs" in completed.stderr + calls = log.read_text(encoding="utf-8").splitlines() + assert any("rev-parse" in call for call in calls) + assert not any("git init" in call or "git fetch" in call for call in calls) + assert not any(call.startswith("docker ") for call in calls) + assert not any("docker build" in call or "docker tag" in call for call in calls) diff --git a/tests/test_contextforge_production_image_contract.py b/tests/test_contextforge_production_image_contract.py index fc66fd4..d8b093e 100644 --- a/tests/test_contextforge_production_image_contract.py +++ b/tests/test_contextforge_production_image_contract.py @@ -3,6 +3,8 @@ from __future__ import annotations import hashlib from pathlib import Path +import yaml + from scripts.promotion_preflight import ( CONTEXTFORGE_PRODUCTION_IMAGE, IMAGE_ENVIRONMENT, @@ -14,15 +16,23 @@ ENV_EXAMPLE = ROOT / "infrastructure" / "compose" / ".env.example" INPUT_MAP = ROOT / "infrastructure" / "compose" / "production-input-map.json" PROD_COMPOSE = ROOT / "infrastructure" / "compose" / "docker-compose.prod.yml" CONTRACT = ROOT / "infrastructure" / "images" / "contextforge" / "runtime-contract.json" +AUTODEPLOY = ROOT / "infrastructure" / "compose" / "docker-compose.autodeploy.yml" + + +def test_governed_contextforge_candidate_uses_the_exact_release_revision() -> None: + document = yaml.safe_load(AUTODEPLOY.read_text(encoding="utf-8")) + service = document["services"]["contextforge"] + assert service["image"] == ( + "itworx-mcp-hub/contextforge-schema:${GITEA_COMMIT_SHA:?GITEA_COMMIT_SHA is required}" + ) + assert service["pull_policy"] == "never" + assert not service.get("build") def test_contextforge_uses_the_verified_local_compatibility_image() -> None: assert "contextforge" not in IMAGE_ENVIRONMENT assert "CONTEXTFORGE_IMAGE" not in REQUIRED_ENV - assert ( - CONTEXTFORGE_PRODUCTION_IMAGE - == "itworx-mcp-hub/contextforge-schema:12d4a0c7789c" - ) + assert CONTEXTFORGE_PRODUCTION_IMAGE == "itworx-mcp-hub/contextforge-schema:12d4a0c7789c" env_example = ENV_EXAMPLE.read_text(encoding="utf-8") assert "CONTEXTFORGE_IMAGE=" not in env_example @@ -42,8 +52,5 @@ def test_contextforge_contract_digest_is_repository_owned_and_exact() -> None: expected = hashlib.sha256(CONTRACT.read_bytes()).hexdigest() production = PROD_COMPOSE.read_text(encoding="utf-8") - assert ( - f'ITWORX_CONTEXTFORGE_EXPECTED_CONTRACT_SHA256: "{expected}"' - in production - ) + assert f'ITWORX_CONTEXTFORGE_EXPECTED_CONTRACT_SHA256: "{expected}"' in production assert "${CONTEXTFORGE_CONTRACT_SHA256" not in production diff --git a/tests/test_ingestion_catalog_compose.py b/tests/test_ingestion_catalog_compose.py index dde1d50..2848db9 100644 --- a/tests/test_ingestion_catalog_compose.py +++ b/tests/test_ingestion_catalog_compose.py @@ -255,7 +255,10 @@ class IngestionCatalogComposeTests(unittest.TestCase): **os.environ, "SECRETS_DIR": str(ROOT / "tests/fixtures"), "CONNECTOR_INGESTION_IMAGE": digest, - "GITEA_COMMIT_SHA": "test-sha", + "GITEA_COMMIT_SHA": "a" * 40, + "APPOPS_SOURCE_TREE": "b" * 40, + "APPOPS_BUILD_ID": "a" * 12, + "SOURCE_DATE_EPOCH": "1790888863", }, capture_output=True, text=True, diff --git a/tests/test_wp220_cve_exceptions.py b/tests/test_wp220_cve_exceptions.py index 482963f..76d7a0c 100644 --- a/tests/test_wp220_cve_exceptions.py +++ b/tests/test_wp220_cve_exceptions.py @@ -3,10 +3,12 @@ from __future__ import annotations import json +import sys from datetime import date, timedelta from pathlib import Path from scripts.check_cve_exceptions import REQUIRED_FIELDS, check +from scripts.draft_cve_exceptions import main as draft_exceptions_main ROOT = Path(__file__).resolve().parents[1] TODAY = date(2026, 8, 15) @@ -45,6 +47,13 @@ def _exception(**overrides) -> dict: return base +def _image_entry(image_id: str, tag: str | None = None) -> dict[str, str]: + entry = {"imageId": image_id} + if tag is not None: + entry["tag"] = tag + return entry + + def test_real_wp220_registry_has_every_required_field() -> None: """The actual drafted registry this packet ships must be schema-complete.""" doc = json.loads( @@ -122,7 +131,7 @@ def test_changed_digest_fails() -> None: exceptions = { "exceptions": [_exception(affectedImageDigests={"control-api": "sha256:" + "a" * 64})] } - current_digests = {"control-api": "sha256:" + "b" * 64} + current_digests = {"control-api": _image_entry("sha256:" + "b" * 64)} errors = check(scan, exceptions, current_digests, TODAY) assert any("DIGEST CHANGED" in e for e in errors) @@ -132,11 +141,258 @@ def test_matching_digest_passes() -> None: exceptions = { "exceptions": [_exception(affectedImageDigests={"control-api": "sha256:" + "a" * 64})] } - current_digests = {"control-api": "sha256:" + "a" * 64} + current_digests = {"control-api": _image_entry("sha256:" + "a" * 64)} errors = check(scan, exceptions, current_digests, TODAY) assert errors == [] +def test_missing_current_service_for_affected_image_fails() -> None: + scan = _scan( + { + "package": "pkg", + "cves": ["CVE-2026-1"], + "severity": "HIGH", + "affectedImages": ["local/control-api:dev", "local/worker:dev"], + } + ) + exceptions = { + "exceptions": [ + _exception( + affectedImageDigests={ + "control-api": "sha256:" + "a" * 64, + "worker": "sha256:" + "b" * 64, + } + ) + ] + } + current = {"control-api": _image_entry("sha256:" + "a" * 64, "local/control-api:dev")} + + errors = check(scan, exceptions, current, TODAY) + + assert any( + "AFFECTED IMAGE UNBOUND" in error and "local/worker:dev" in error for error in errors + ) + + +def test_missing_exception_digest_binding_fails_closed() -> None: + scan = _scan( + { + "package": "pkg", + "cves": ["CVE-2026-1"], + "severity": "HIGH", + "affectedImages": ["local/control-api:dev"], + } + ) + exceptions = { + "exceptions": [ + _exception( + affectedImageDigests={ + "unrelated": "sha256:" + "c" * 64, + } + ) + ] + } + current = {"control-api": _image_entry("sha256:" + "a" * 64, "local/control-api:dev")} + + errors = check(scan, exceptions, current, TODAY) + + assert any("IMAGE BINDING MISSING" in error and "control-api" in error for error in errors) + + +def test_empty_exception_digest_binding_fails_closed() -> None: + scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"}) + exceptions = {"exceptions": [_exception(affectedImageDigests={"control-api": ""})]} + current = {"control-api": _image_entry("sha256:" + "a" * 64)} + + errors = check(scan, exceptions, current, TODAY) + + assert any("IMAGE BINDING MISSING" in error and "control-api" in error for error in errors) + + +def test_malformed_recorded_digest_fails_closed() -> None: + scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"}) + exceptions = {"exceptions": [_exception(affectedImageDigests={"control-api": "sha256:abc"})]} + current = {"control-api": _image_entry("sha256:" + "a" * 64)} + + errors = check(scan, exceptions, current, TODAY) + + assert any("IMAGE BINDING INVALID" in error and "control-api" in error for error in errors) + + +def test_missing_or_malformed_current_image_id_fails_even_with_digest() -> None: + scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"}) + exceptions = {"exceptions": [_exception()]} + current = {"control-api": {"digest": "sha256:" + "a" * 64}} + + errors = check(scan, exceptions, current, TODAY) + + assert any("CURRENT IMAGE ID INVALID" in error and "control-api" in error for error in errors) + + +def test_malformed_current_image_id_fails_even_with_digest() -> None: + scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"}) + exceptions = {"exceptions": [_exception()]} + current = { + "control-api": { + "imageId": "sha256:not-a-full-image-id", + "digest": "sha256:" + "a" * 64, + } + } + + errors = check(scan, exceptions, current, TODAY) + + assert any("CURRENT IMAGE ID INVALID" in error for error in errors) + + +def test_empty_current_image_inventory_fails_when_supplied() -> None: + scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"}) + + errors = check(scan, {"exceptions": [_exception()]}, {}, TODAY) + + assert any("IMAGE INVENTORY INVALID" in error for error in errors) + + +def test_scan_installed_version_must_match_reviewed_version() -> None: + scan = _scan( + { + "package": "pkg", + "cves": ["CVE-2026-1"], + "severity": "HIGH", + "installedVersion": "2.0", + } + ) + + errors = check(scan, {"exceptions": [_exception(installedVersion="1.0")]}, None, TODAY) + + assert any("VERSION MISMATCH" in error and "2.0" in error for error in errors) + + +def test_conflicting_duplicate_exceptions_fail_instead_of_overwriting() -> None: + scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"}) + approved = _exception() + conflicting = _exception( + approvalStatus="pending", riskOwner="", approver="", compensatingControls="" + ) + + errors = check(scan, {"exceptions": [approved, conflicting]}, None, TODAY) + + assert any("CONFLICTING DUPLICATE" in error for error in errors) + assert any("UNAPPROVED" in error for error in errors) + + +def test_scoped_approval_binds_only_affected_services() -> None: + scan = _scan( + { + "package": "pkg", + "cves": ["CVE-2026-1"], + "severity": "HIGH", + "installedVersion": "1.0", + "affectedImages": ["local/worker:dev"], + } + ) + exceptions = {"exceptions": [_exception(affectedImageDigests={"worker": "sha256:" + "b" * 64})]} + current = { + "control-api": _image_entry("sha256:" + "c" * 64, "local/control-api:dev"), + "worker": _image_entry("sha256:" + "b" * 64, "local/worker:dev"), + } + + errors = check(scan, exceptions, current, TODAY) + + assert errors == [] + + +def test_legacy_scan_without_affected_images_uses_full_inventory() -> None: + scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"}) + exceptions = { + "exceptions": [_exception(affectedImageDigests={"control-api": "sha256:" + "a" * 64})] + } + current = { + "control-api": _image_entry("sha256:" + "a" * 64), + "worker": _image_entry("sha256:" + "b" * 64), + } + + errors = check(scan, exceptions, current, TODAY) + + assert any("IMAGE BINDING MISSING" in error and "worker" in error for error in errors) + + +def test_legacy_scan_rejects_inventory_missing_a_reviewed_service() -> None: + scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"}) + exceptions = { + "exceptions": [ + _exception( + affectedImageDigests={ + "control-api": "sha256:" + "a" * 64, + "worker": "sha256:" + "b" * 64, + } + ) + ] + } + current = {"control-api": _image_entry("sha256:" + "a" * 64)} + + errors = check(scan, exceptions, current, TODAY) + + assert any("IMAGE INVENTORY SCOPE CHANGED" in error for error in errors) + + +def test_draft_is_pending_and_scopes_digests_to_affected_images( + tmp_path: Path, monkeypatch +) -> None: + scan_path = tmp_path / "scan.json" + image_path = tmp_path / "images.json" + output_path = tmp_path / "draft.json" + scan_path.write_text( + json.dumps( + { + **_scan( + { + "package": "pkg", + "cves": ["CVE-2026-1"], + "severity": "HIGH", + "installedVersion": "1.0", + "affectedImages": ["local/worker:dev"], + } + ), + "scannedAt": "2026-08-15T00:00:00Z", + } + ), + encoding="utf-8", + ) + image_path.write_text( + json.dumps( + { + "images": { + "control-api": _image_entry("sha256:" + "a" * 64, "local/control-api:dev"), + "worker": _image_entry("sha256:" + "b" * 64, "local/worker:dev"), + } + } + ), + encoding="utf-8", + ) + monkeypatch.setattr( + sys, + "argv", + [ + "draft_cve_exceptions.py", + "--scan-summary", + str(scan_path), + "--image-digests", + str(image_path), + "--evidence-reference", + "evidence/WP-200/test.md", + "--output", + str(output_path), + ], + ) + + assert draft_exceptions_main() == 0 + drafted = json.loads(output_path.read_text(encoding="utf-8"))["exceptions"] + + assert len(drafted) == 1 + assert drafted[0]["approvalStatus"] == "pending" + assert drafted[0]["affectedImageDigests"] == {"worker": "sha256:" + "b" * 64} + + def test_malformed_exception_missing_required_field_fails() -> None: scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"}) broken = _exception() @@ -158,3 +414,103 @@ def test_multiple_cves_in_one_package_row_each_need_their_own_exception() -> Non errors = check(scan, exceptions, None, TODAY) assert not any("CVE-2026-1" in e for e in errors) assert any("CVE-2026-2" in e and "UNAPPROVED" in e for e in errors) + + +def test_distinct_installed_versions_are_reviewed_separately_for_scoped_images() -> None: + scan = _scan( + { + "package": "pkg", + "cves": ["CVE-2026-1"], + "severity": "HIGH", + "installedVersion": "1.0", + "affectedImages": ["local/web:dev"], + }, + { + "package": "pkg", + "cves": ["CVE-2026-1"], + "severity": "HIGH", + "installedVersion": "2.0", + "affectedImages": ["local/postgres:dev"], + }, + ) + exceptions = { + "exceptions": [ + _exception(affectedImageDigests={"web": "sha256:" + "a" * 64}), + _exception( + id="pkg-CVE-2026-1-v2", + installedVersion="2.0", + affectedImageDigests={"postgres": "sha256:" + "b" * 64}, + ), + ] + } + images = { + "web": _image_entry("sha256:" + "a" * 64, "local/web:dev"), + "postgres": _image_entry("sha256:" + "b" * 64, "local/postgres:dev"), + } + assert check(scan, exceptions, images, TODAY) == [] + exceptions["exceptions"].pop() + assert any("VERSION MISMATCH" in error for error in check(scan, exceptions, images, TODAY)) + + +def test_versionless_legacy_scan_rejects_ambiguous_version_reviews() -> None: + scan = _scan({"package": "pkg", "cves": ["CVE-2026-1"], "severity": "HIGH"}) + exceptions = {"exceptions": [_exception(), _exception(id="v2", installedVersion="2.0")]} + assert any("AMBIGUOUS VERSION" in error for error in check(scan, exceptions, None, TODAY)) + + +def test_draft_distinct_versions_keeps_unique_pending_image_bound_reviews( + tmp_path: Path, monkeypatch +) -> None: + scan = _scan( + *[ + { + "package": "pkg", + "cves": ["CVE-2026-1"], + "severity": "HIGH", + "installedVersion": version, + "affectedImages": [f"local/{service}:dev"], + } + for version, service in [("1.0", "web"), ("2.0", "postgres")] + ] + ) + scans, images, output = ( + tmp_path / "scan.json", + tmp_path / "images.json", + tmp_path / "draft.json", + ) + scans.write_text(json.dumps(scan), encoding="utf-8") + images.write_text( + json.dumps( + { + "images": { + "web": _image_entry("sha256:" + "a" * 64, "local/web:dev"), + "postgres": _image_entry("sha256:" + "b" * 64, "local/postgres:dev"), + } + } + ), + encoding="utf-8", + ) + monkeypatch.setattr( + sys, + "argv", + [ + "draft", + "--scan-summary", + str(scans), + "--image-digests", + str(images), + "--output", + str(output), + "--evidence-reference", + "fixture", + ], + ) + assert draft_exceptions_main() == 0 + drafted = json.loads(output.read_text(encoding="utf-8"))["exceptions"] + assert len(drafted) == 2 and len({entry["id"] for entry in drafted}) == 2 + assert all( + entry["approvalStatus"] == "pending" and not entry["riskOwner"] and not entry["approver"] + for entry in drafted + ) + assert drafted[0]["affectedImageDigests"] == {"web": "sha256:" + "a" * 64} + assert drafted[1]["affectedImageDigests"] == {"postgres": "sha256:" + "b" * 64}