feat(cli): ca2a start, run the reference transport from a config file - #52
Conversation
imran-siddique
left a comment
There was a problem hiding this comment.
Reviewed the diff and thread. Scope is tight and matches the body: live inbound A2A JSON-RPC wiring for the second Tier 2 checkbox, with ordinary A2A returning ca2a: null rather than inventing a trust state, and malformed metadata plus denials failing closed with a structured ca2a_code. X25519 key handling validates length and is software-configured only, with the docs correctly caveating that it is not measurement-bound. No secrets in the diff; test keys are generated at runtime. Tests cover config, policy loader, and server paths, and all real checks are green. LGTM. Leaving the merge to a maintainer.
|
@Susanpdl this is approved and ready except for a merge conflict with main. Could you merge main into feat/live-a2a-inbound-serving (or rebase) and resolve the conflict? Once it is clean it can go in. Thanks. |
|
Happy to resolve the conflict, but I want to check the shape with you first, because #53 landed after your approval here and it overlaps with this branch. The conflict itself is small: CHANGELOG.md, LIMITATIONS.md, ROADMAP.md, docs/spec/transport.md, and pyproject.toml. No code conflicts. The bigger question is duplication. #53 added What is still missing on main and is the part of this PR I think is worth keeping:
So my suggestion is to refit this branch instead of merging it as-is: keep Want me to go that way, or would you rather keep the two transports separate (JSON-RPC alongside the reference server), or just close this and open a smaller CLI-only PR? Happy to do whichever you prefer. |
|
@carloshvp refit onto #53, ready for another look. Short version: your reference transport made my Starlette listener redundant, so I deleted it rather than shipping two servers. The Three judgment calls I would like you to check:
Claim boundary is unchanged: software mode is 237 tests pass, ruff/mypy/bandit clean, and I smoke-tested it against a real socket. Unrelated, but worth knowing: running pytest rewrites |
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment Thanks for integrating Codecov - We've got you covered ☂️ |
imran-siddique
left a comment
There was a problem hiding this comment.
Approving. All real checks pass on every platform and Python version; the only red is the maintainer-hold gate.
Reviewed the substance rather than the diffstat, and the two decisions that mattered are both right:
Provider selection fails closed. auto never degrades to software-only, and a named hardware provider whose device node is absent is a startup error rather than a silent downgrade. That is the same posture the rest of this stack holds to, and it is the difference between a software-mode listener being a stated configuration and being an accident.
Loopback default. Changing listen_addr from 0.0.0.0:8443 to 127.0.0.1:8443 was the important call, and you made it unprompted with the right reasoning: the old value was harmless while nothing read it, and the moment ca2a start binds it, defaulting to every interface would put a peer on the network by omission. Requiring the host explicitly is better than a permissive default with a warning.
Also good: load_policy refusing to start without one of policy_bundle_path or local_policy, since a node with no policy intersects every delegated scope to nothing and that reads as misconfiguration rather than a decision. Relative-path resolution against the config directory, the empty-file check, and OSError wrapped into ConfigError are all the right level of care for a config path.
Test coverage is proportionate (274 lines of tests for 216 of source), and the CHANGELOG is honest about what this does not do, including that attestation remains one-directional and software mode is still the default posture.
Leaving the merge to @imran-siddique.
|
Heads up: this has drifted into conflict with main since the approval on July 28, so it cannot merge as it stands. The approval holds, it just needs a rebase onto current main. Ping me once it is clean and I will take another look at the merge. |
The transport spec listed serving as "Not yet: serving is via transport.server.serve", so there was no way to run a peer from a config file. ca2a start resolves a policy and an attestation provider from the config, builds a PeerNode, and serves it with transport.server. No new dependencies, since the reference transport is standard library only. The wiring lives in ca2a_runtime.bootstrap (load_policy, select_provider, build_peer_node) so it is usable without the CLI, and so the CLI adds no capability the library did not already have. Provider selection fails closed. software-only carries no hardware guarantee, so auto refuses to start when no confidential-computing platform is detected rather than silently choosing it, and a named hardware provider whose device node is absent is a startup error rather than a failure at the first handshake. Default listen_addr to 127.0.0.1:8443 and require an explicit host. It was 0.0.0.0:8443 while nothing read it; now that something binds it, defaulting to every interface would put a peer on the network by omission. transport.server.serve already defaulted to loopback, so the two now agree. Claim boundary is unchanged: software mode is assurance="none" and the seal is not bound to a hardware-verified measurement. Signed-off-by: Susan Poudel <susanpdl77@gmail.com>
10f3804 to
ddb9f2f
Compare
|
@imran-siddique rebased onto current main ( I collapsed the branch to a single commit while I was in there. The old history still had the Starlette listener commit followed by the merge that deleted it, so replaying it onto main would have reintroduced and then re-removed a server that no longer exists, with conflicts at each step. The net diff is unchanged: the same 575 insertions and 29 deletions across the same 14 files, and diffing the two patches shows only blob hashes and context lines from the new upstream content. Locally: 278 passed, 3 skipped, and ruff, mypy, and bandit are clean on the scopes CI uses. Worth flagging on CI, since the checks are red. Nothing there is a test failure. One job fails, It also cannot be fixed from this repo. Our floor is For what it is worth, the advisory looks unreachable from here. PYSEC-2026-3552 is about PKCS#7 The remaining red is the maintainer-approval gate, since the force-push dismissed your July 28 approval. |
imran-siddique
left a comment
There was a problem hiding this comment.
Re-approving after the rebase. The re-run is green across all six matrix jobs; the earlier red was pip-audit on cryptography 49.0.0 (PYSEC-2026-3552), not this branch.
Reviewed the refit rather than assuming it. Deleting your Starlette listener once the reference transport landed was the right call, and dropping the [serve] extra with it keeps one server in the tree instead of two. select_provider failing closed is the part that matters: 'auto' raising rather than falling back to software-only means a missing device node can never quietly become a no-guarantee posture.
Closes the
ca2a startgap the transport spec listed as "Not yet: serving is viatransport.server.serve".What changed since the first review
This branch originally shipped its own Starlette JSON-RPC listener. #53 landed a reference HTTP transport,
PeerNode, and the attestation handshake while this was open, which made that listener duplicate work and, worse, seal against a key nobody had appraised. So the branch is refit rather than rebased: the Starlette server, the[serve]extra, and the starlette/uvicorn/httpx dependencies are gone. What is left is the config-driven entry point.What it does
ca2a start --config ca2a-config.yamlresolves a policy and an attestation provider from the config file, builds aPeerNode, and serves it withca2a_runtime.transport.server. No new dependencies, since the reference transport is standard library only.The wiring lives in
ca2a_runtime.bootstrap(load_policy,select_provider,build_peer_node) so it is usable without the CLI, and so the CLI adds no capability the library did not already have. A program that already holds aPolicyand a provider should keep constructing aPeerNodedirectly.Decisions worth a look
Provider selection fails closed, following the rule in
tee/software.pythat a no-guarantee posture is never a silent fallback.autorefuses to start when no confidential-computing platform is detected instead of quietly pickingsoftware-only, and a named hardware provider whose device node is absent is a startup error rather than a failure at the first handshake. Software mode prints the assurance a caller will actually appraise, so an operator does not have to infer it.enclave_private_key_hexis dropped. APeerNodegenerates its own X25519 channel keypair and publishes the public half through the handshake, so a pre-shared key in a config file would mean sealing to something nobody appraised. That was the right model before #53 and the wrong one after it.listen_addrnow defaults to127.0.0.1:8443and must name a host explicitly. It was0.0.0.0:8443while nothing read it; now that something binds it, defaulting to every interface would put a peer on the network by omission.transport.server.servealready defaulted to loopback, so the two now agree. Bad addresses failca2a validate-configrather than at bind time, and bracketed IPv6 parses.Claim boundary
Unchanged from #53. Software mode is
assurance="none", and the seal is not bound to a hardware-verified measurement.ca2a startdoes not move that line, it just makes the existing software-mode path runnable from a config file. LIMITATIONS.md, ROADMAP.md, and the transport spec are updated to say exactly that.Tests
237 pass, 3 skipped. New coverage in
tests/unit/test_bootstrap.py(policy resolution, provider fail-closed behavior, and a config-built node serving a real HTTP call) andtests/unit/test_cli_start.py(whatca2a startbinds, and the four ways it refuses to start). ruff, mypy, and bandit are clean.Also smoke-tested against a real socket: the handshake endpoint returns the offer, an unknown path 404s, and a port conflict exits 1 with
cannot bind 127.0.0.1:8443instead of a traceback.One thing for you
Running the suite rewrites
examples/rejection-with-proof/chain.jsonanddag.jsonwith fresh keys, since the test executesdemo.pyand the demo generates keypairs. I reverted the churn here, but it will bite anyone who runs pytest before committing. Happy to open a separate issue.