feat(aea-ci): generic check-third-party-hashes + configurable generate-api-docs - #876
Conversation
…e-api-docs Adds a new aea-ci command and makes generate-api-docs reusable by downstream repos (e.g. open-autonomy). New command: aea-ci check-third-party-hashes Verifies local packages/packages.json third_party entries against one or more upstream repos specified as --upstream owner/repo@version (repeatable). A package is OK if any upstream has a matching hash. Reports mismatches (present with wrong hash) and missing packages (absent from every upstream). aea-ci generate-api-docs — now configurable Previously hard-coded to open-aea's layout. Now accepts --source-dir, --packages-dir, --plugins-dir, --docs-dir, --default-package, --ignore-plugin, --ignore-prefix, --parallel. Defaults preserve the existing open-aea behaviour so check-api-docs and generate-api-documentation tox envs keep working unchanged. Internals refactored from module-level constants to an ApiDocsConfig dataclass. Also hoists cli.py imports to module scope. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
aea-ci-helpers is intentionally usable without open-aea installed (the dependencies_checks CI job installs only the plugin and runs aea-ci check-pyproject before aea is available). The previous commit hoisted aea.configurations.base / aea.helpers.git / aea_ci_helpers. generate_api_docs to module scope, which meant every CLI command pulled in open-aea at import time. Revert those specific imports to inline in the generate_api_docs handler with a top-of-module comment explaining why. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR extends open-aea-ci-helpers to be reusable by downstream repos (e.g. open-autonomy) by adding a generic third-party hash verification command and making API docs generation configurable (paths/ignores/parallelism), while keeping defaults compatible with the current open-aea layout.
Changes:
- Added
aea-ci check-third-party-hashescommand + implementation to validate localpackages/packages.json:third_partyhashes against one or more upstream repos. - Refactored
generate-api-docsto use anApiDocsConfigdataclass and added CLI options for source/packages/plugins/docs dirs, ignore lists, default packages, and--parallel. - Updated CLI wiring/tests and added
requestsas a dependency.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| plugins/aea-ci-helpers/aea_ci_helpers/check_third_party_hashes.py | New implementation for fetching upstream packages.json and comparing third-party hashes. |
| plugins/aea-ci-helpers/aea_ci_helpers/cli.py | Adds check-third-party-hashes and extends generate-api-docs with configurable options. |
| plugins/aea-ci-helpers/aea_ci_helpers/generate_api_docs.py | Refactors API docs generation to be configurable and optionally parallel. |
| plugins/aea-ci-helpers/setup.py | Adds requests dependency needed for upstream fetching. |
| plugins/aea-ci-helpers/tests/test_check_third_party_hashes.py | New unit tests for parsing/loading/comparison and exit-code behavior. |
| plugins/aea-ci-helpers/tests/test_aea_ci_cli.py | Ensures new command is registered in the CLI smoke tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
OjusWiZard
left a comment
There was a problem hiding this comment.
I found one correctness issue not yet covered in the existing review comments.
ReviewClean, well-scoped, well-tested (29 tests, 17 new, all mocked — no live network). Naturally extends the recent ci-helpers extraction ( Issues worth addressing1. Parallel mode silently swallows errors (real bug). In executor.submit(make_pydoc, dotted_path, doc_file)The returned 2. Tension with the recent "restore eager aea imports" decision ( 3. Hardcoded RAW_PACKAGES_URL = "https://raw.githubusercontent.com/{repo}/v{version}/packages/packages.json"A user passing 4. 5. Error reporting in Nits
What's good
RecommendationApprove once #1 (parallel-mode error swallowing) is fixed — that's the only real correctness issue. #2–#5 are cleanup; can be addressed in the same PR or follow-ups. |
Addresses review on #876. check_third_party_hashes.py * Upstream.tag_version strips any leading v so both @2.2.0 and @v2.2.0 build the same URL (LOCKhart07 comment). * fetch_upstream_packages wraps requests.RequestException and JSON decoding errors in RuntimeError with a diagnostic snippet (Copilot comments). * Raise RuntimeError when packages.json has no dev section instead of silently returning the whole top-level dict (LOCKhart07 comment). * load_local_third_party catches FileNotFoundError / JSONDecodeError and raises RuntimeError with the offending path (Copilot comment). * run() now fetches every upstream via a helper that tolerates per-upstream failures. The check fails only when every upstream is unreachable, or when a reachable upstream produces a real mismatch (LOCKhart07 comment). Missing from every reachable upstream is also treated as a failure since the package cannot be verified. cli.py * Wraps ComponentType(type_str) in try/except and raises click.BadParameter on invalid --default-package values (Copilot comment). * Consolidates the lazy imports under a single pylint disable for readability. setup.py * Bumps requests lower bound to >=2.32.5 to match open-aea root pyproject.toml (OjusWiZard comment). tests * Adds TestFetchUpstreamPackages covering URL building, non-200, transport errors, malformed JSON, dev+third_party merging, and missing-dev handling (LOCKhart07 comment). * Adds tests for malformed / missing local packages.json and for the new one-upstream-flaky, another-matches tolerance rule. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
generate_api_docs.py
* Parallel mode now collects every submitted Future and calls
.result() before executor.shutdown, so any exception raised by a
worker surfaces to the caller instead of being silently dropped.
Serial mode behaviour is unchanged (D. Minarsch #1).
* _submit renamed to _dispatch and now appends to a futures
accumulator; the helper functions take it as a parameter (#1 / nit).
* should_skip messages now include the offending path so it is
clear which file was skipped (nit).
check_third_party_hashes.py
* Migrates from the third-party ``requests`` library to
``aea.helpers.http_requests`` per open-aea policy (D. Minarsch
comment on setup.py: "no requests allowed").
* ``run()`` uses ``click.echo(..., err=True)`` for warnings and
errors instead of raw ``print`` (#5).
* Adds a one-line comment explaining the dev + third_party merge
(nit).
cli.py
* ``check-third-party-hashes`` handler now lazy-imports its module
(which transitively imports ``aea.*``), matching the pattern for
``generate-api-docs`` and preserving ``aea-ci-helpers``' ability
to run without ``open-aea`` installed (#2 / #4). Also keeps the
CLI startup light.
setup.py
* Drops the ``requests`` install_requires now that the plugin no
longer uses it.
tests
* Patches ``aea_ci_helpers.check_third_party_hashes.http_requests.get``
instead of the previous ``requests.get`` target, and raises
``http_requests.ConnectionError`` for the transport-failure case.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously ``long_description`` was hardcoded to the same one-liner as ``description``, leaving the ``Description`` section on PyPI empty — pypi.org/project/open-aea-ci-helpers/2.2.0 currently shows no body. Switch ``long_description`` to the README contents so the plugin's PyPI page gets rendered installation + command docs on the next release. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pypi.org/project/open-aea/#description was empty for 2.2.0 because the
root pyproject.toml has no readme key, so Poetry's build backend did
not emit a Description body in PKG-INFO. Every plugin setup.py had the
same bug from a different angle: long_description was hardcoded to the
same one-liner as description, so each plugin page shipped a minimal
description instead of the README contents.
Fix both uniformly:
* Root: add readme = "README.md" to [tool.poetry].
* Every plugin setup.py: define a small _read_long_description()
helper that reads the plugin's README.md and pass that to
long_description.
Verified locally by rebuilding the sdist/wheel for every package and
checking PKG-INFO / METADATA now contains Description-Content-Type:
text/markdown plus the README body (body lengths 169-1771 per plugin,
and the root sdist PKG-INFO now includes the README).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LOCKhart07
left a comment
There was a problem hiding this comment.
Re-review
All prior review points addressed:
- Parallel-mode error swallowing fixed (
futures.append(...)+fut.result()ingenerate_api_docs). aea.*import kept lazy —check_third_party_hashesis now lazy-imported inside its CLI handler too, socheck-pyprojectstill runs withoutopen-aeainstalled.Upstream.tag_versionstrips a leadingv, so@v2.2.0and@2.2.0both work, with tests.requestsdependency removed; migrated toaea.helpers.http_requests, andinstall_requirestrimmed accordingly.click.echo(..., err=True)used throughoutrun().should_skipmessages include the offending path.- Per-upstream tolerance is the right behaviour and is now directly covered by
test_one_unreachable_but_other_matches_is_okandtest_missing_from_reachable_upstreams_yields_exit_1. - Uniform PyPI long-description fix across the root
pyproject.tomland all 10 pluginsetup.pyfiles; all plugins have aREADME.mdand aMANIFEST.inthat includes it, so sdist/wheel installs won't break.
One non-blocking gap
The parallel-mode fix (my prior #1) has no regression test. The serial-vs-parallel failure semantics are the whole point of the fix, and without a test a future refactor can silently re-break --parallel. A small test that patches make_pydoc (or _dispatch) to raise and asserts generate_api_docs(cfg_with_parallel=True) re-raises would pin the behaviour. Worth adding in this PR or a follow-up; not blocking the merge.
…CN image Background: test_dht.py was skipped on the libp2p v0.8 -> v0.33 bump because the deployed valory/open-acn-node:latest image was still a libp2p v0.8 build and wire-incompatible with the new connection code. The ACN has since been rebuilt at valory/open-acn (Go 1.24 + libp2p v0.33.2) — the first image compatible with this branch. Point the docker fixture, CI pull step and comments at the new image via the ``:latest`` alias (published by valory-xyz/open-acn#22) and strip the blanket skip marker from the Local variants. While verifying locally against the new image, found a second wire compatibility issue: libp2p v0.33 refuses to dial a ``/dns4/0.0.0.0/...`` multiaddr ("no good addresses"), so every entry- peer URI that the fixture built from the bind-all wildcard would fail to bootstrap. v0.8 tolerated 0.0.0.0 as a dial target. Switch META_ADDRESS to 127.0.0.1 in both acn_image.py and conftest.py — the value is used both for binds (loopback is fine) and for peer entry points (loopback is now required). Public variants still rely on the production fetchai/valory ACN nodes, which have not yet been redeployed; keep them skipped with a dedicated marker until upstream redeploys. TestDHTRobustness already has its own CI-skip marker; no other gating needed for it. Verified the new ACN image locally: * boots and emits the expected peer ID * two-container bootstrap succeeds with /dns4/127.0.0.1/ multiaddrs Full pytest run cannot complete on macOS because Docker Desktop does not expose network="host" ports to the host loopback; Linux CI will exercise the end-to-end path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reflects the changes in the previous commit: ACN image rebuilt at valory/open-acn (new repo) with a ``:latest`` alias from valory-xyz/open-acn#22, 0.0.0.0 → 127.0.0.1 dial-format fix, narrower skip marker that now only guards the Public variants. Also bump the test_libp2p conftest copyright year to pass check-copyright on the edits from the previous commit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test_libp2p connection fingerprint depends on the contents of conftest.py and acn_image.py; the 0.0.0.0 → 127.0.0.1 switch and the ACN image tag update changed the fingerprint, so packages.json and docs/package_list.md needed the regeneration the CI hash-check flagged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The link, cited in aea/skills/tasks.py as a docstring reference to a Python bug and auto-generated into docs/api/skills/tasks.md, resolves fine today but 503s intermittently from GitHub Actions runners. The Python bug tracker has been deprecated in favour of GitHub Issues and flakes regularly. Add it to the ``-u`` (status-skip) list to stop spurious check-doc-links-hashes failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OjusWiZard
left a comment
There was a problem hiding this comment.
Looks good overall — nice refactor of aea-ci-helpers for downstream reuse, and the new hash-check command plus test coverage are strong. I read the full changed-file set for this PR and I'm approving with a few non-blocking inline comments for follow-up hardening/clarity.
|
Approved after a full changed-file review, leaving the three non-blocking notes here for follow-up:
|
|
Post-merge review — flagging a few things worth a follow-up. Strengths
Issues worth a follow-up1. Possible Local/Public skip-selection bug in if base_cls is None:
test_cls = type(name, bases, {})
else:
test_cls = type(name, (base_cls,), {})
test_cls = skip_public_acn_not_upgraded(test_cls)Previously the skip was unconditional. Intent is Local runs / Public skipped, so this only works if 2. 3. 4. Asymmetric 5. Implicit string concatenation reads as accidental in a few spots. 6. 7. Minor style: Security
Bottom lineGenuinely useful PR — the new command, the PyPI fix, and the parallel-mode exception-surfacing fix are all real improvements. Main thing to verify is that the |
…cleanup Bumps every ``open-aea*`` pin from ``==2.2.0`` to ``==2.2.1`` and wires the OA CI into the matching upstream commands now shipped with ``open-aea-ci-helpers 2.2.1`` (valory-xyz/open-aea#876 / #877). ### aea-helpers plugin: removed forks ``check-third-party-hashes`` and ``generate-api-docs`` used to live in ``plugins/aea-helpers/aea_helpers/`` as OA-local forks. Both have been promoted to upstream ``aea-ci-helpers`` (configurable + generic), so: * Delete ``plugins/aea-helpers/aea_helpers/check_third_party_hashes.py`` * Delete ``plugins/aea-helpers/aea_helpers/generate_api_docs.py`` * Unregister the two commands from ``aea-helpers/cli.py`` + add a NOTE pointing at the upstream replacements. * Tox ``[testenv:check-third-party-hashes]`` now calls ``aea-ci check-third-party-hashes --upstream valory-xyz/open-aea@2.2.1`` * Tox ``[testenv:check-api-docs]`` and ``[testenv:generate-api-documentation]`` now call ``aea-ci generate-api-docs`` with OA-specific ``--source-dir``, ``--packages-dir``, ``--plugins-dir``, ``--parallel`` and the 8 canonical ``--default-package`` entries (abci, gnosis_safe, ...). ### Flashbots cleanup (minimal surface) ``open-aea-ledger-ethereum-flashbots`` was removed upstream in open-aea 2.2.1. We remove all dead-on-arrival call sites but INTENTIONALLY LEAVE the skill-level ``use_flashbots`` parameter in place to avoid a wire-format / public-API breaking change: * ``autonomy/replay/agent.py``: dropped the ``cp ethereum_private_key.txt -> ethereum_flashbots_private_key.txt`` and ``aea add-key ethereum-flashbots`` calls. These would fail at runtime now that the plugin is gone. * ``plugins/aea-helpers/aea_helpers/check_dependencies.py``: dropped ``open-aea-flashbots`` from the Pipfile ignore list. * ``plugins/aea-helpers/aea_helpers/bump_dependencies.py``: dropped the ``open-aea-ledger-ethereum-flashbots`` entry. ``packages/valory/skills/*`` is NOT touched: * ``abstract_round_abci/behaviour_utils.py`` still has the ``FLASHBOTS_LEDGER_ID`` constant, ``use_flashbots`` parameter, and the ``if use_flashbots:`` dispatch branch in ``_send_transaction_request``. * ``transaction_settlement_abci/behaviours.py`` still plumbs ``self.use_flashbots`` through ``_get_tx_data``. * ``transaction_settlement_abci/payload_tools.py`` still encodes and decodes ``use_flashbots`` / ``raise_on_failed_simulation`` in the on-chain payload bytes — removing them would shift field offsets by 32 bytes and silently corrupt every in-flight transaction exchanged with an agent still on 2.2.0. Behaviour: agents whose payloads/calls use the default ``use_flashbots=False`` keep working unchanged. Agents that explicitly opt in to ``use_flashbots=True`` will fail at transaction dispatch because the ``ethereum_flashbots`` ledger is no longer registered — the right signal to pin 2.2.0 or bundle their own flashbots support. ### Third-party hash realignment ``valory/ledger:0.19.0`` fingerprint changed upstream (its ``connection.yaml`` lost the ``ethereum_flashbots`` ledger-apis block). Updated the pinned hash in ``packages.json``, re-synced the local package via ``autonomy packages sync --update-packages``, and re-locked. ``aea-ci check-third-party-hashes`` validates: ``All 8 third-party hashes are consistent with upstream@v2.2.1``. ### Other * ``poetry.lock`` regenerated for 2.2.1 pins. * API docs regenerated via upstream ``aea-ci generate-api-docs``. * ``docs/package_list.md`` hashes regenerated via ``aea-ci check-doc-hashes --fix``. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Started as an extension of
open-aea-ci-helpersto unblock downstream refactors in open-autonomy#2470; expanded under review to cover three adjacent packaging / testing fixes that turned up during verification. Breakdown below.1.
aea-ci-helpers— new & updated CLI commandsDownstream repos (e.g.
open-autonomy) were carrying their own forks of these utilities; the changes here make them reusable.aea-ci check-third-party-hashes— verifies a localpackages/packages.jsonthird_partymap against one or more upstream repos, supplied as--upstream owner/repo@version(repeatable,@vprefix tolerated). A package is OK if any upstream has a matching hash; reachable upstream with a wrong hash = mismatch; package absent from every reachable upstream = missing (exit 1); all upstreams unreachable = exit 1; a single flaky upstream is tolerated as long as another responds. Usesaea.helpers.http_requests(norequestsdep). Errors/warnings go to stderr viaclick.echo(err=True).aea-ci generate-api-docsnow configurable — new options--source-dir,--packages-dir,--plugins-dir,--docs-dir,--default-package,--ignore-plugin,--ignore-prefix,--parallel. Defaults preserve current open-aea behaviour socheck-api-docsandgenerate-api-documentationtox envs are unaffected. Internals refactored from module-level constants into anApiDocsConfigdataclass; parallel mode now collects submitted futures and.result()s them so worker exceptions actually surface instead of being silently dropped.cli.pyimports hoisted to module scope where safe; lazy where a command transitively importsaea.*(soaea-ci-helpersstays installable withoutopen-aea).check_third_party_hashes; rest are smoke tests for every CLI command).Downstream usage
aea-ci check-third-party-hashes --upstream valory-xyz/open-aea@2.2.0 aea-ci generate-api-docs \ --source-dir autonomy --packages-dir packages --plugins-dir plugins --parallel2. PyPI descriptions populated from README (root + 10 plugins)
pypi.org/project/open-aea/#descriptionwas empty for 2.2.0 because the rootpyproject.tomlhad noreadmekey, so Poetry emitted noDescriptionbody in PKG-INFO. Every pluginsetup.pyhad the same bug from a different angle:long_descriptionwas hardcoded to the same one-liner asdescription, so each plugin page shipped a minimal description instead of the README.Fix applied uniformly:
pyproject.toml:readme = "README.md"under[tool.poetry].setup.pyfiles: add a local_read_long_description()helper reading the plugin's ownREADME.md.Verified locally by rebuilding every package and checking
PKG-INFO/METADATAnow containsDescription-Content-Type: text/markdownplus the README body. Plugin description-body lengths went from 46–92 bytes (hardcoded one-liner) to 169–1771 bytes (actual README).3.
test_libp2pDHT integration tests — re-enabled against rebuilt ACNtest_dht.pywas blanket-skipped on the libp2p v0.8 → v0.33 bump because the deployedvalory/open-acn-node:latestimage was still a v0.8 build and wire-incompatible with this branch. The ACN has since been rebuilt at a new Docker repovalory/open-acn(Go 1.24, libp2p v0.33.2), now publishing a:latestalias tracking main courtesy of valory-xyz/open-acn#22.docker pullstep atvalory/open-acn:latest./dns4/0.0.0.0/...multiaddrs ("no good addresses"). SwitchMETA_ADDRESSfrom0.0.0.0→127.0.0.1inacn_image.pyandconftest.pyso the entry-peer multiaddrs the fixture builds are actually dialable.skip_acn_docker_mismatchwithskip_public_acn_not_upgraded, applied only to the Public variants (those still dial the production fetchai/valory ACN nodes, which haven't been redeployed yet). Local variants andTestDHTRobustnessnow run.valory/test_libp2pfingerprint (packages.json + docs/package_list.md + connection.yaml) since the fixture edits changed fingerprinted content.Full pytest run can't complete on macOS (Docker Desktop doesn't expose
network="host"ports to the host loopback) — Linux CI exercises the end-to-end path.4. Small CI hygiene
bugs.python.org/issue8296added tocheck-doc-links-hashes-uskip list. The link is cited in a docstring (aea/skills/tasks.py→ auto-generated intodocs/api/skills/tasks.md), but the tracker has been deprecated in favour of GitHub Issues and 503s intermittently from GHA IP ranges, causing spurious doc-link failures.Test plan
pytest plugins/aea-ci-helpers/tests/— 42 pass (17 new)tox -e black-check -e isort-check -e flake8 -e darglint -e check-copyrightmypy plugins/aea-ci-helpers/aea_ci_helpers(new files clean; 4 pre-existing errors incheck_dependencies.py/check_pyproject.pyare unrelated to this PR)pylint— 10/10 on all modified/new filesvalory/open-acn:latest, booted, confirmed libp2p v0.33.2 and expected peer IDs; verified two-container bootstrap with/dns4/127.0.0.1/multiaddrsaea packages lockclean (hashes regenerated)aea-ci check-pyprojectstill works withoutaeainstalled (preserves the CI job that installs only the plugin)open-autonomy#2470can pin the newopen-aea-ci-helpers