Skip to content

fix(rest/python): read signing_keys[] where the schema places it and drop the ucp.keys[] mirror - #228

Open
vishkaty wants to merge 1 commit into
Universal-Commerce-Protocol:mainfrom
vishkaty:fix/samples-keys-capabilities-py
Open

fix(rest/python): read signing_keys[] where the schema places it and drop the ucp.keys[] mirror#228
vishkaty wants to merge 1 commit into
Universal-Commerce-Protocol:mainfrom
vishkaty:fix/samples-keys-capabilities-py

Conversation

@vishkaty

@vishkaty vishkaty commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes #227

This is the Python twin of #226, the Node fix for the identical defect
named in #225. Both servers reached it by the same two steps, called out
in that issue: here #122 introduced the wrong reader and #169 added the
matching mirror, as #162 and #179 did on the Node side.

Observed vs expected

This server declares UCP_VERSION 2026-04-08
(routes/discovery_profile.json). At that pin,
source/discovery/profile_schema.json $defs/base requires ucp and
separately declares signing_keys as a top level sibling of ucp; that
schema defines no keys field anywhere. discovery.py published
signing_keys[] correctly at the top level but also mirrored it into a
nested ucp.keys[] with no schema basis at any pin, and _extract_keys()
in ucp_signing.py read only that nested field. Since ucp is required on
every real profile document, a reader that looks inside ucp whenever it
is present can never see a top level sibling field on any real document,
so the verifier could not resolve the schema correct field the same
server publishes.

Fix

  • ucp_signing.py: _extract_keys() now reads the top level
    signing_keys field directly instead of descending into ucp first.
    Comment states the schema citation and flags that when this server
    moves UCP_VERSION to 2026-08-25, the field name must move to keys in
    the same change, per ucp#566.
  • routes/discovery.py: the ucp object no longer carries a keys
    member. Top level signing_keys[] is unchanged.
  • dependencies.py: doc comment fixed, keys[] to signing_keys[].
  • README.md: the two lines describing key discovery updated to match,
    keys[] to signing_keys[] in the request signature section, and the
    webhook section no longer claims the ucp.keys[] mirror.
  • ucp_signing_test.py: ProfileFetchTest and ExtractKeysTest rewritten
    around the schema correct top level shape; new tests prove nested
    ucp.keys/ucp.signing_keys are not read, and that a top level
    keys[] (the 2026-08-25 name) is not read yet either, since this
    server has not moved its declared version.
  • signature_integration_test.py: the shared good profile fixture used by
    the request-signature verification tests moved from
    {"ucp": {"keys": [...]}} to {"ucp": {}, "signing_keys": [...]},
    matching the schema correct shape. This fixture was missed by an
    initial grep for .keys since it is a bare dict key, not an attribute
    access.
  • integration_test.py: test_profile_publishes_the_webhook_signing_key
    now asserts ucp.keys is absent instead of asserting it is present.
  • client/flower_shop/signing.py: LocalProfileServer, the counterparty profile owned by
    the demo happy path client, moved from the same nested
    ucp.keys shape to top level signing_keys, so the sign then verify
    loop it exists to demonstrate actually round-trips against the fixed
    reader.

Verification

  • TDD throughout: a standalone reproduction against the unfixed code
    confirmed both the read side and write side defect first (mirroring
    the linked issue), then every new assertion was confirmed red for the
    stated reason before the fix and green after.
  • ucp_signing.py unit tests: 77/77 before, 80/80 after (3 new tests).
  • The discovery integration test
    (test_profile_publishes_the_webhook_signing_key): passing before and
    after, with its assertion strengthened from requiring the mirror to
    forbidding it.
  • Two kill tests on the final branch, each reverting one piece of the fix
    in isolation with the new tests kept: reverting _extract_keys to the
    old nested ucp.keys read fails 4 tests in ucp_signing_test.py; reintroducing the ucp.keys
    mirror in discovery.py fails the discovery test. Both restored to green
    after.
  • Live round trip: booted the server, fetched the live
    /.well-known/ucp document, confirmed ucp.keys is absent and top level
    signing_keys[] is present, confirmed the fixed _extract_keys
    on this same server resolves the served document, and validated the
    document with Python jsonschema (Draft202012Validator, real $ref
    resolution across the full vendored release/2026-04-08 schema tree)
    against source/discovery/profile_schema.json: VALID.
  • Ran the demo happy path client, which signs every request and
    publishes its own JWK through a local profile server, against the
    fixed server with --allow_insecure_profile_urls: the server logs
    "RFC 9421 signature verified" for every signed request, proving the
    full sign then verify loop this demo exists to show now round-trips
    end to end.
  • pre-commit (pinned .pre-commit-config.yaml) run on every touched file:
    clean.

Notes for reviewers

@damaz91 damaz91 added the status:needs-triage Signal that the PR is ready for human triage label Sep 1, 2026
@carolinerg1 carolinerg1 added devops status:under-review and removed status:needs-triage Signal that the PR is ready for human triage labels Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python reference verifier cannot discover the signing keys of a schema correct peer at its own declared version

4 participants