diff --git a/.github/workflows/daily-agentrx-trace-optimizer.lock.yml b/.github/workflows/daily-agentrx-trace-optimizer.lock.yml index 38dca2c0479..456aadf1aa7 100644 --- a/.github/workflows/daily-agentrx-trace-optimizer.lock.yml +++ b/.github/workflows/daily-agentrx-trace-optimizer.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"75de6a850ce462b55f09797ea5b48add2a32a5fe7cba9ed7bf94b3b60a2fdd10","body_hash":"2c8a43e76e9a38ea4e5def7bb83f40ccbe4fd45cddc42490e187dbce02b39354","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.296"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a67a80b14c20fc72cc749d8ce0afd5dc503485a29974f5e02bc687f30203f35e","body_hash":"2c8a43e76e9a38ea4e5def7bb83f40ccbe4fd45cddc42490e187dbce02b39354","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.296"}} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"9000827ccba6bdab643e8b6fd33ac0654aef8333","version":"v8.0.2"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"949feb2413d6458794dcd2491c4babbbce0c15c1","version":"v7.1.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"cf430e030ddbb5b0abf93d22962f4752f3646cd9","version":"v7.0.2"},{"repo":"astral-sh/setup-uv","sha":"1c37ad07a6a961277cf70c0d37d6f313000f5884","version":"v10.3.0"},{"repo":"docker/build-push-action","sha":"c3c9e263c25d99ce0380d002d59b67737d91b0dc","version":"v7.4.0"},{"repo":"docker/setup-buildx-action","sha":"f87e5991a6d7451dcb8d9637bfbc97413f497069","version":"v4.4.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.70","digest":"sha256:b15ab48cbbab02904eb9bbd359a398db59690eebbea1e6934d47ed2bb407dd34","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.70@sha256:b15ab48cbbab02904eb9bbd359a398db59690eebbea1e6934d47ed2bb407dd34"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.70","digest":"sha256:9ac8f9c7294e892c916de3da9d2588dfd8cd4931e5c39b00143266d7ed9fd52a","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.70@sha256:9ac8f9c7294e892c916de3da9d2588dfd8cd4931e5c39b00143266d7ed9fd52a"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.70","digest":"sha256:b5bc53f4becfd96e926e63c83831f1b3c9512f713c83161d2b8c9b7204b3e917","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.70@sha256:b5bc53f4becfd96e926e63c83831f1b3c9512f713c83161d2b8c9b7204b3e917"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.36","digest":"sha256:b4d2856018959c798b3e3f81ae4ab5b275e06354e123e1f7971de931f2f450f2","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.36@sha256:b4d2856018959c798b3e3f81ae4ab5b275e06354e123e1f7971de931f2f450f2"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:41835613992df88f51e923bad486f96dfb2e6cda29f13e217df41833284ae382","pinned_image":"ghcr.io/github/gh-aw-node@sha256:41835613992df88f51e923bad486f96dfb2e6cda29f13e217df41833284ae382"},{"image":"ghcr.io/github/github-mcp-server:v2.0.2","digest":"sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796","pinned_image":"ghcr.io/github/github-mcp-server:v2.0.2@sha256:ffced0d76e77428532a2ced185516992d77fd146d69eec2f244e6237d1a97796"}],"mcp_servers":[{"name":"agenticworkflows","tools":["*"]},{"name":"github","tools":["get_commit","get_file_contents","get_latest_release","get_me","get_pull_request","get_pull_request_comments","get_pull_request_diff","get_pull_request_files","get_pull_request_review_comments","get_pull_request_reviews","get_pull_request_status","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_pull_requests","list_releases","list_starred_repositories","list_tags","pull_request_read","search_code","search_issues","search_pull_requests","search_repositories"]},{"name":"safeoutputs","tools":["create_discussion","create_issue","noop","report_incomplete","report_missing_data","report_missing_tool"]}],"threat_detection":{"mode":"enabled"},"admission":true} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -183,7 +183,7 @@ jobs: GH_AW_EXPERIMENT_SPEC: '{"sub_agent_strategy":{"variants":["sub_agents","single_agent"],"description":"Test whether delegating trajectory-builder, artifacts-summarizer, and failure-pattern-classifier to small-model sub-agents improves recommendation quality vs. inline analysis by the main agent","hypothesis":"H0: no change in issue quality or run success rate. H1: sub_agents variant yields higher evidence completeness score with equal or lower token cost","metric":"issue_evidence_completeness","secondary_metrics":["run_success_rate","ai_credits_total","run_duration_ms"],"guardrail_metrics":[{"name":"empty_output_rate","threshold":"\u003c=0.10"},{"name":"noop_rate","threshold":"\u003c=0.30"}],"min_samples":20,"weight":[50,50],"start_date":"2026-06-02"}}' GH_AW_EXPERIMENT_STATE_FILE: /tmp/gh-aw/experiments/state.jsonl GH_AW_EXPERIMENT_STATE_DIR: /tmp/gh-aw/experiments - GH_AW_HARNESS_VERSION: 75de6a850ce462b55f09797ea5b48add2a32a5fe7cba9ed7bf94b3b60a2fdd10:2c8a43e76e9a38ea4e5def7bb83f40ccbe4fd45cddc42490e187dbce02b39354 + GH_AW_HARNESS_VERSION: a67a80b14c20fc72cc749d8ce0afd5dc503485a29974f5e02bc687f30203f35e:2c8a43e76e9a38ea4e5def7bb83f40ccbe4fd45cddc42490e187dbce02b39354 with: script: | const path = require('path'); @@ -525,7 +525,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "75de6a850ce462b55f09797ea5b48add2a32a5fe7cba9ed7bf94b3b60a2fdd10:2c8a43e76e9a38ea4e5def7bb83f40ccbe4fd45cddc42490e187dbce02b39354" + GH_AW_ADMISSION_WORKFLOW_HASH: "a67a80b14c20fc72cc749d8ce0afd5dc503485a29974f5e02bc687f30203f35e:2c8a43e76e9a38ea4e5def7bb83f40ccbe4fd45cddc42490e187dbce02b39354" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} @@ -716,7 +716,7 @@ jobs: AGENTRX_HOME: /tmp/gh-aw/python/agentrx UV_PYTHON_INSTALL_DIR: /tmp/gh-aw/python/uv-python name: Install AgentRx with a sandbox-compatible CPython - run: "set -euo pipefail\n# The agent sandbox cannot run the runner's CPython (glibc mismatch) and only\n# ships PyPy, which has no wheels for AgentRx dependencies. Install a portable\n# uv-managed CPython plus AgentRx under /tmp/gh-aw, which is mounted read-write\n# into the sandbox.\nrm -rf \"$AGENTRX_HOME\"\nmkdir -p \"$AGENTRX_HOME\"\ngit init \"$AGENTRX_HOME/src\"\ngit -C \"$AGENTRX_HOME/src\" remote add origin https://github.com/microsoft/AgentRx.git\ngit -C \"$AGENTRX_HOME/src\" fetch --depth 1 origin 7a18c79708e7671be15124460f4f7296107c2a55\ngit -C \"$AGENTRX_HOME/src\" checkout --detach FETCH_HEAD\nuv venv --python 3.12 --python-preference only-managed \"$AGENTRX_HOME/.venv\"\nuv pip install --python \"$AGENTRX_HOME/.venv/bin/python\" \"$AGENTRX_HOME/src\"\n\"$AGENTRX_HOME/.venv/bin/python\" \"$AGENTRX_HOME/src/run.py\" --help >/dev/null" + run: "set -euo pipefail\n# The agent sandbox cannot run the runner's CPython (glibc mismatch) and only\n# ships PyPy, which has no wheels for AgentRx dependencies. Install a portable\n# uv-managed CPython plus AgentRx under /tmp/gh-aw, which is mounted read-write\n# into the sandbox.\nrm -rf \"$AGENTRX_HOME\"\nmkdir -p \"$AGENTRX_HOME\"\ngit init \"$AGENTRX_HOME/src\"\ngit -C \"$AGENTRX_HOME/src\" remote add origin https://github.com/microsoft/AgentRx.git\ngit -C \"$AGENTRX_HOME/src\" fetch --depth 1 origin 7a18c79708e7671be15124460f4f7296107c2a55\ngit -C \"$AGENTRX_HOME/src\" checkout --detach FETCH_HEAD\nuv venv --python 3.12 --python-preference only-managed \"$AGENTRX_HOME/.venv\"\n# run.py imports AgentRx from the pinned checkout directly. Sync its complete\n# dependency lock without executing a source build or resolving new versions.\nuv pip sync --require-hashes --only-binary=:all: --python \"$AGENTRX_HOME/.venv/bin/python\" \"${GITHUB_WORKSPACE}/.github/workflows/requirements-agentrx.txt\"\n\"$AGENTRX_HOME/.venv/bin/python\" \"$AGENTRX_HOME/src/run.py\" --help >/dev/null" - name: Configure Git credentials env: @@ -2007,7 +2007,7 @@ jobs: GH_AW_DEFAULT_ADMISSION_URL: ${{ vars.GH_AW_DEFAULT_ADMISSION_URL }} GH_AW_DEFAULT_ADMISSION_AUDIENCE: ${{ vars.GH_AW_DEFAULT_ADMISSION_AUDIENCE }} GH_AW_DEFAULT_ADMISSION_ON_OUTAGE: ${{ vars.GH_AW_DEFAULT_ADMISSION_ON_OUTAGE }} - GH_AW_ADMISSION_WORKFLOW_HASH: "75de6a850ce462b55f09797ea5b48add2a32a5fe7cba9ed7bf94b3b60a2fdd10:2c8a43e76e9a38ea4e5def7bb83f40ccbe4fd45cddc42490e187dbce02b39354" + GH_AW_ADMISSION_WORKFLOW_HASH: "a67a80b14c20fc72cc749d8ce0afd5dc503485a29974f5e02bc687f30203f35e:2c8a43e76e9a38ea4e5def7bb83f40ccbe4fd45cddc42490e187dbce02b39354" GH_AW_INFO_WORKFLOW_ID: ${{ job.workflow_ref }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }} diff --git a/.github/workflows/daily-agentrx-trace-optimizer.md b/.github/workflows/daily-agentrx-trace-optimizer.md index 28407fb116b..dec2614bf12 100644 --- a/.github/workflows/daily-agentrx-trace-optimizer.md +++ b/.github/workflows/daily-agentrx-trace-optimizer.md @@ -56,7 +56,9 @@ steps: git -C "$AGENTRX_HOME/src" fetch --depth 1 origin 7a18c79708e7671be15124460f4f7296107c2a55 git -C "$AGENTRX_HOME/src" checkout --detach FETCH_HEAD uv venv --python 3.12 --python-preference only-managed "$AGENTRX_HOME/.venv" - uv pip install --python "$AGENTRX_HOME/.venv/bin/python" "$AGENTRX_HOME/src" + # run.py imports AgentRx from the pinned checkout directly. Sync its complete + # dependency lock without executing a source build or resolving new versions. + uv pip sync --require-hashes --only-binary=:all: --python "$AGENTRX_HOME/.venv/bin/python" "${GITHUB_WORKSPACE}/.github/workflows/requirements-agentrx.txt" "$AGENTRX_HOME/.venv/bin/python" "$AGENTRX_HOME/src/run.py" --help >/dev/null safe-outputs: mentions: false diff --git a/.github/workflows/requirements-agentrx.txt b/.github/workflows/requirements-agentrx.txt new file mode 100644 index 00000000000..5e7f6fcd938 --- /dev/null +++ b/.github/workflows/requirements-agentrx.txt @@ -0,0 +1,110 @@ +# AgentRx 7a18c79708e7671be15124460f4f7296107c2a55 runtime dependencies. +# Linux x86_64 / CPython 3.12 wheels only; no source builds. +# Refresh from the pinned checkout's requirements.txt: +# uv pip compile requirements.txt --python-version 3.12 --python-platform x86_64-manylinux_2_28 --only-binary=:all: -o requirements-pinned.txt +# python -m pip download --no-deps --only-binary=:all: --python-version 3.12 --implementation cp --abi cp312 --platform manylinux_2_28_x86_64 --platform manylinux2014_x86_64 --dest wheels -r requirements-pinned.txt +# python -m pip hash wheels/*.whl +aiohappyeyeballs==2.7.1 \ + --hash=sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472 +aiohttp==3.14.5 \ + --hash=sha256:20f085697d7e911f1f73c43ed03fafbed1e7121797e2eb5428efa80398060584 +aiosignal==1.4.0 \ + --hash=sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e +annotated-types==0.8.0 \ + --hash=sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0 +anyio==4.15.1 \ + --hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101 +attrs==26.1.0 \ + --hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 +azure-ai-inference==1.0.0b9 \ + --hash=sha256:49823732e674092dad83bb8b0d1b65aa73111fab924d61349eb2a8cdc0493990 +azure-core==1.41.0 \ + --hash=sha256:522b4011e8180b1a3dcd2024396a4e7fe9ac37fb8597db47163d230b5efe892d +azure-identity==1.26.0 \ + --hash=sha256:3c60d9682b3ac01507388d474cb5a5ffbe54152a96b6cdcaa7013da9315cf186 +certifi==2026.10.19 \ + --hash=sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b +cffi==2.1.1 \ + --hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf +charset-normalizer==3.5.2 \ + --hash=sha256:3d31298449090ab8d47b7b1b2a555ff73cac7ed438a08b7ac160980c7ebed649 +contourpy==1.4.0 \ + --hash=sha256:875f42444c9cf48d56f724f2637e60d0f73b3b12c9041e1484580a233edf9591 +cryptography==50.0.2 \ + --hash=sha256:4061c0079120205fb760c58acab6443e217307dcf05e3702cf970e0689972856 +cycler==0.12.1 \ + --hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 +fonttools==4.66.1 \ + --hash=sha256:7b8ff9e0edbcee2fbf7dff0c41b9041c1901c26acf64e23adb67495012df11de +frozenlist==1.8.0 \ + --hash=sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383 +h11==0.16.0 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 +httpcore2==2.13.1 \ + --hash=sha256:e1e05d4f25f7d7d496bfb96748f6f4b67657b03da069b3a68c36069f3db73d0a +httpx2==2.13.1 \ + --hash=sha256:6dff50fabc270ee5fd25d845d0b078ed20564579744d6d962850975996d2f9a4 +idna==3.20 \ + --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c +iniconfig==2.3.1 \ + --hash=sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7 +isodate==0.7.2 \ + --hash=sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15 +jiter==0.17.0 \ + --hash=sha256:8c21265b251d99bbb40080d178a8953e35601d3a1564e05c4de4c0d2ca616797 +kiwisolver==1.5.1 \ + --hash=sha256:34633ecf50d16187ab8e5528b7a2530f2feb4e23f300db4672538b51cfc5cd38 +matplotlib==3.11.2 \ + --hash=sha256:df4f7784aca81a94f254c0a2767d592ee25f407e488f5fa7203e51093fb6ca27 +msal==1.39.0 \ + --hash=sha256:2d2577886906cd7293850dffa2da29119966c213bfc6ec0cecf8bf7621e1ca77 +msal-extensions==1.3.1 \ + --hash=sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca +multidict==7.1.0 \ + --hash=sha256:fed6b7705d49dd07e5e0dd5f5c873fc44047e92d714299b13245b5fecac49d01 +numpy==2.5.4 \ + --hash=sha256:fbde6962867ee75b48b0ee29b2b9372ec5d617799dbaf38e82dc0596f2f7738a +openai==3.31.0 \ + --hash=sha256:e5839f6670483f368de40ce3422f524c1afaf12ff8660539f53489e200db8d78 +packaging==26.3 \ + --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c +pillow==12.3.0 \ + --hash=sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91 +pluggy==1.6.0 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 +propcache==0.5.4 \ + --hash=sha256:2814ecd8e818f487bee4b0f921bc4d1c176cc5fc71ac0f072d0fa67eda4ac14b +pycparser==3.11 \ + --hash=sha256:51d5a8ba2be0bbe440b99d2112604c95bbbc3c2748a64260186c541e1729cd80 +pydantic==2.14.1 \ + --hash=sha256:9195d967ec791692a04438115466764fb8b9a27b31f14a760437694f40d6b454 +pydantic-core==2.50.1 \ + --hash=sha256:93ba4e9d8210d941c200431a56b2c0400b131865947903937ed3ec5404307d2e +pygments==2.21.0 \ + --hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 +pyjwt==2.15.1 \ + --hash=sha256:42d59d631f7768a1028a64c7ff581a9bf7519804daf91fc5b6c56e30eec5e193 +pyparsing==3.3.3 \ + --hash=sha256:ece8c00a69cf01b45d0b1dedabb469c90d8caf996d4fda40f147627a122849a4 +pytest==9.1.1 \ + --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c +python-dateutil==2.9.0.post0 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 +python-dotenv==1.2.4 \ + --hash=sha256:42269a8a5b3fd54ffa6f3d84b18abed50064717576b4ecf03dc4a55d8aa04fdc +requests==2.34.2 \ + --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 +sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 +truststore==0.10.5 \ + --hash=sha256:9aaaedaefaf06d8b206278cf8b5012bc897f485a874503501e12d776df78951c +typing-extensions==4.16.0 \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 +typing-inspection==0.4.4 \ + --hash=sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147 +urllib3==2.8.0 \ + --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 +yarl==1.25.1 \ + --hash=sha256:c6f117789d22dce188e5754e8bc65b7e6ebf8cb73963b9fa761f672a5883769d diff --git a/.gitignore b/.gitignore index 74699fe1110..3e148565a98 100644 --- a/.gitignore +++ b/.gitignore @@ -192,6 +192,7 @@ test-inline-*.lock.yml *.txt !docs/public/robots.txt !docs/public/.well-known/ai.txt +!.github/workflows/requirements-agentrx.txt conclusion/ detection/ diff --git a/pkg/workflow/agentrx_workflow_test.go b/pkg/workflow/agentrx_workflow_test.go new file mode 100644 index 00000000000..943f26df4f6 --- /dev/null +++ b/pkg/workflow/agentrx_workflow_test.go @@ -0,0 +1,78 @@ +//go:build !integration + +package workflow + +import ( + "os" + "regexp" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestAgentRxSetupUsesHashedDependencyLock(t *testing.T) { + source, err := os.ReadFile("../../.github/workflows/daily-agentrx-trace-optimizer.md") + require.NoError(t, err) + parts := strings.SplitN(string(source), "---", 3) + require.Len(t, parts, 3) + var frontmatter struct { + Steps []struct { + Name string `yaml:"name"` + Run string `yaml:"run"` + } `yaml:"steps"` + } + require.NoError(t, yaml.Unmarshal([]byte(parts[1]), &frontmatter)) + require.NotEmpty(t, frontmatter.Steps) + setup := frontmatter.Steps[0].Run + require.Contains(t, setup, `uv pip sync --require-hashes --only-binary=:all: --python "$AGENTRX_HOME/.venv/bin/python" "${GITHUB_WORKSPACE}/.github/workflows/requirements-agentrx.txt"`) + require.NotContains(t, setup, "uv pip install", "installing the source project dynamically resolves build and runtime dependencies") + require.Contains(t, setup, `"$AGENTRX_HOME/.venv/bin/python" "$AGENTRX_HOME/src/run.py" --help`) + + compiled, err := os.ReadFile("../../.github/workflows/daily-agentrx-trace-optimizer.lock.yml") + require.NoError(t, err) + var workflow struct { + Jobs map[string]struct { + Steps []struct { + Name string `yaml:"name"` + Run string `yaml:"run"` + } `yaml:"steps"` + } `yaml:"jobs"` + } + require.NoError(t, yaml.Unmarshal(compiled, &workflow)) + var compiledSetup string + for _, step := range workflow.Jobs["agent"].Steps { + if step.Name == frontmatter.Steps[0].Name { + compiledSetup = step.Run + } + } + require.Equal(t, strings.TrimSpace(setup), compiledSetup, "the emitted workflow must preserve the hash-enforced setup") + + lock, err := os.ReadFile("../../.github/workflows/requirements-agentrx.txt") + require.NoError(t, err) + revision := regexp.MustCompile(`fetch --depth 1 origin ([0-9a-f]{40})`).FindStringSubmatch(setup) + require.Len(t, revision, 2, "AgentRx source must remain pinned to an immutable commit") + require.Contains(t, string(lock), "# AgentRx "+revision[1]+" runtime dependencies.") + + entry := regexp.MustCompile(`^[a-z0-9][a-z0-9.-]*==[0-9][a-zA-Z0-9.]* \\\n --hash=sha256:[0-9a-f]{64}$`) + var requirement strings.Builder + count := 0 + for line := range strings.SplitSeq(string(lock), "\n") { + if strings.HasPrefix(line, "#") || strings.TrimSpace(line) == "" { + continue + } + if requirement.Len() > 0 { + requirement.WriteByte('\n') + } + requirement.WriteString(line) + if strings.HasSuffix(line, `\`) { + continue + } + require.Regexp(t, entry, requirement.String(), "every dependency must have an exact version and a SHA-256 wheel hash") + requirement.Reset() + count++ + } + require.Zero(t, requirement.Len(), "dependency entries must not be truncated") + require.Positive(t, count) +}