MODELMARKET.DEV / BLOG
← All posts

7 Nov 2026 · 5 min read

A missing version broke verification of signed ARD search results

#ai#agents#webdev#opensource

A weather search on modelmarket.dev returned a result with a trustManifest. Its signed subject named a version. The search result had no version field.

A verifier comparing the two had to reject it. The complete catalog entry was fine; the serializer used for search had dropped a field that the signature covered.

That happened on November 4, during the rollout of ARD discovery to six AIMarket hubs. A second deployment fixed the missing field. The steps below follow the same public discovery path that exposed it, from a domain name to the description of a callable tool.

ARD means Agent Resource Discovery. The implementation follows parts of two October 30 proposals: Initial Contact revision 00 and Manifest and Lookup revision 00. Both are individual Internet-Drafts, still work in progress. Datatracker records no IETF endorsement or approved-standard status.

Follow the catalog from a domain

Start with modelmarket.dev/.well-known/ard.json. It lists four resources: the registry, the hub's A2A card, its MCP card, and its AIMarket discovery document. The registry leads to the capabilities for sale:

domain → manifest → registry → capability descriptor → existing invoke endpoint

With curl and jq installed, fetch that manifest and extract the registry address. All the commands here read public catalog data, including the POST used for search; they incur no capability charges.

manifest=$(curl -fsS \
  https://modelmarket.dev/.well-known/ard.json)

printf '%s\n' "$manifest" |
  jq '.entries[] | {displayName, type, url}'

registry=$(printf '%s\n' "$manifest" |
  jq -r '.entries[]
    | select(.type == "application/ai-registry+json")
    | .url')

Search for weather in Paris:

results=$(curl -fsS "$registry/search" \
  -H 'Content-Type: application/json' \
  --data '{"query":{"text":"weather in Paris"},"pageSize":3}')

printf '%s\n' "$results" |
  jq '.results[] |
    {displayName, identifier, version, source, score, url}'

Each result gives you a url to follow. The catalog changes, so take the address from the response:

descriptor_url=$(printf '%s\n' "$results" |
  jq -r '.results[0].url // empty')

test -n "$descriptor_url" &&
  curl -fsS "$descriptor_url" |
  jq '{input_schema, output_schema, price_per_call_usd, invoke}'

For a listing page instead of a text search:

curl -fsS "$registry/agents?pageSize=1" | jq .

The descriptor shows input and output schemas, prices, and an invoke object. That object's body already identifies the product, capability and source hub; its input is empty for the buyer to fill in.

The actual call still goes to POST /ai-market/v2/invoke. ARD added a route into the existing catalog. Execution, payments and receipts continue through AIMarket v2.

Read the source alongside the score

A weather search can return both a capability published by this hub and one indexed from a peer. Look at source before treating them alike.

Five of the six deployments sit at a domain root. Each signs its own entries with its existing Ed25519 key under a did:web identity. A peer's entry keeps that peer's publisher identity in its identifier and names the peer as source; the indexing hub leaves it unsigned. It is an address to investigate, with publisher authority still to establish.

The sixth installation, at independentai.network/hub, has no did:web identity configured in this implementation and serves unsigned entries too. Its origin-level well-known address redirects to the manifest under /hub.

A publisher signature lets a consumer check who signed the entry's coordinates and whether the fetched descriptor matches its digest. Judging the service's answers or its price needs other evidence. The score in these search results only ranks relevance.

That mixture of signed and unsigned results explains why the version bug survived the first checks. Weather searches on the other hubs returned unsigned peer rows. They exercised lookup and descriptor retrieval without exercising the signed local-result path. The main hub's local weather result reached the missing comparison.

The serializer now includes version, and the test compares the entire signed subject with the result rather than checking selected fields. After the second rollout, a verifier fetched the live DID documents and descriptor bytes over the internet, checked signatures and compared digests. The main hub passed 43 checks; all six deployments passed their applicable checks, including the unsigned installation. This was an outside check of this implementation, with cross-vendor client testing still to do.

Where the draft and the tools needed interpretation

Schema validation took another detour. The published CDDL grammar, which describes the expected data structures, would not compile unchanged with cddl 0.12.14. It redefined prelude names, and the tool stopped on the content-delivery group choice inside an entry map.

The conformance folder keeps the original grammar alongside a documented, checkable copy. The latter removes the conflicting definitions and uses two map alternatives for delivery. Manifest, listing-page, search-request and search-response samples then validated. An entry missing its URL and a search score of 101 were rejected as expected.

One condition still needed an application test: the open extension map let the grammar accept an entry carrying both url and data. Passing these sample checks therefore covers less than complete conformance.

There is also an algorithm mismatch for client authors to account for. These hubs sign with EdDSA; the discovery draft makes ES256 mandatory to implement. An ES256-only verifier cannot verify these entries. A client following this walkthrough needs EdDSA support to check their signatures.

We operate AIMarket. AI assistance was used to prepare this article from implementation, tests, and deployment records.

More field notes