From 83040ab702eb7716316cfa5d0259ae62edeec63a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Apr 2026 21:54:14 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(registry):=20P0=20follow-ups=20?= =?UTF-8?q?=E2=80=94=20type=20reclassification=20guard,=20docs=20IA,=20sou?= =?UTF-8?q?rce=20trust=20clarity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #3538 https://claude.ai/code/session_01LpZQ7dcUTFgreLrUkKsLht --- ...egistry-p0-followups-type-guard-docs-ia.md | 10 ++++ docs.json | 6 +- docs/registry/index.mdx | 20 ++++++- docs/registry/registering-an-agent.mdx | 57 +++++++++++++++++++ scripts/generate-openapi.ts | 2 +- server/src/crawler.ts | 2 +- server/src/routes/registry-api.ts | 26 +++++---- server/src/schemas/registry.ts | 20 ++++++- static/openapi/registry.yaml | 18 +++--- 9 files changed, 132 insertions(+), 29 deletions(-) create mode 100644 .changeset/registry-p0-followups-type-guard-docs-ia.md create mode 100644 docs/registry/registering-an-agent.mdx diff --git a/.changeset/registry-p0-followups-type-guard-docs-ia.md b/.changeset/registry-p0-followups-type-guard-docs-ia.md new file mode 100644 index 0000000000..178671a136 --- /dev/null +++ b/.changeset/registry-p0-followups-type-guard-docs-ia.md @@ -0,0 +1,10 @@ +--- +--- + +Registry P0 follow-ups from #3495: fix crawler type reclassification guard so agents with +wrong non-unknown types can be corrected on future crawl passes; rename OpenAPI tag from +"Lookups & Authorization" to "Authorization Lookups" to fix the %26-encoded URL slug; add +trust-level descriptions to source/member/discovered_from schema fields; document auth-aware +visibility on /operator; add missing operator/publisher rows to docs/registry/index.mdx +overview table; add docs/registry/registering-an-agent.mdx explaining the two registration +paths and trust levels. diff --git a/docs.json b/docs.json index 93d9efe6d5..3a81afaab2 100644 --- a/docs.json +++ b/docs.json @@ -505,7 +505,8 @@ "directory": "docs/registry/api-reference" }, "pages": [ - "docs/registry/index" + "docs/registry/index", + "docs/registry/registering-an-agent" ] }, "docs/reference/gmsf-reference", @@ -997,7 +998,8 @@ "directory": "docs/registry/api-reference" }, "pages": [ - "docs/registry/index" + "docs/registry/index", + "docs/registry/registering-an-agent" ] }, "docs/reference/gmsf-reference", diff --git a/docs/registry/index.mdx b/docs/registry/index.mdx index 03de740bed..b14ccfd1c5 100644 --- a/docs/registry/index.mdx +++ b/docs/registry/index.mdx @@ -84,9 +84,12 @@ Rate-limited endpoints return `429 Too Many Requests` when the limit is exceeded Poll a cursor-based feed of registry changes for local sync. - + Look up agents by domain, validate product authorization, and check property authorization in real time. + + How agents appear in the registry, what trust level each path produces, and what membership unlocks. + ### Brand Resolution @@ -139,10 +142,12 @@ All sources produce the same resolution response structure. To get full brand id | GET | `/api/registry/feed` | Poll cursor-based registry change feed (auth required) | -### Lookups & Authorization +### Authorization Lookups | Method | Path | Description | |--------|------|-------------| +| GET | `/api/registry/operator` | What agents does this domain operate? (auth-aware — see [operator lookup](#operator-and-publisher-lookup)) | +| GET | `/api/registry/publisher` | What does this domain publish and which agents does it authorize? | | GET | `/api/registry/lookup/domain/{domain}` | Find agents authorized for a domain | | GET | `/api/registry/lookup/property` | Find agents by property identifier | | GET | `/api/registry/lookup/agent/{agentUrl}/domains` | Get all domains for an agent | @@ -150,6 +155,17 @@ All sources produce the same resolution response structure. To get full brand id | POST | `/api/registry/expand/product-identifiers` | Expand property selectors to identifiers | | GET | `/api/registry/validate/property-authorization` | Real-time authorization check | +#### Operator and publisher lookup + +`/api/registry/operator` and `/api/registry/publisher` answer different questions about the same domain: + +| Endpoint | Question | Typical caller | +|----------|----------|----------------| +| `/api/registry/operator?domain=X` | What agents does this entity operate? Which publishers trust it? | Buy-side: validating a sell-side counterparty | +| `/api/registry/publisher?domain=X` | What inventory does this entity publish? Which agents does it authorize? | Sell-side: inspecting a publisher's authorization set | + +The operator endpoint returns more data for authenticated callers: anonymous callers see public agents only; callers with an AgenticAdvertising.org API-access token additionally see `members_only` agents; callers who are the profile owner additionally see `private` agents. [See registering an agent](/docs/registry/registering-an-agent) for how trust levels map to registration source. + Authorization validation checks both sides: the publisher's `adagents.json` (does it authorize this agent with the claimed `delegation_type`?) and the operator's `brand.json` (does it declare this property with a matching `relationship`?). ### Validation Tools diff --git a/docs/registry/registering-an-agent.mdx b/docs/registry/registering-an-agent.mdx new file mode 100644 index 0000000000..9f273fde42 --- /dev/null +++ b/docs/registry/registering-an-agent.mdx @@ -0,0 +1,57 @@ +--- +title: Registering an Agent +sidebarTitle: Registering an Agent +description: "How agents appear in the AgenticAdvertising.org registry, what trust level each path produces, and what AgenticAdvertising.org membership unlocks." +"og:title": "AdCP — Registering an Agent" +--- + +Agents appear in the AgenticAdvertising.org registry through two paths. The path determines the agent's `source` value in API responses and the trust level assigned to it. + +## Registration paths + +| Path | How | `source` in API | Trust level | +|------|-----|-----------------|-------------| +| **Member direct registration** | An AgenticAdvertising.org member organization enrolls the agent via the member dashboard. The member has accepted AgenticAdvertising.org terms on the agent's behalf. | `registered` | Full — appears in `public`, `members_only`, and `private` tiers depending on visibility settings | +| **Discovery via adagents.json** | A member publisher lists the agent in their `adagents.json`. The registry crawler finds and indexes it automatically. The agent itself has not opted in. | `discovered` | Public only — always visible as a `public` agent, regardless of caller authentication | + +## What trust level looks like in API responses + +The `source` field is returned on every agent in `GET /api/registry/agents` and on the `operators` list in `GET /api/registry/operator`. + +```json Registered agent (member direct) +{ + "url": "https://ads.streamhaus.example.com", + "type": "sales", + "source": "registered", + "member": { "slug": "streamhaus", "display_name": "StreamHaus" } +} +``` + +```json Discovered agent (via publisher adagents.json) +{ + "url": "https://agent.examplepub.com", + "type": "sales", + "source": "discovered", + "discovered_from": { "publisher_domain": "examplepub.com" } +} +``` + +A `discovered` agent's `member` field is absent. It was listed by a publisher, not enrolled by the agent operator. If you are the operator of a `discovered` agent and want to upgrade to `registered` status, the path is AgenticAdvertising.org membership (see below). + +## What AgenticAdvertising.org membership unlocks + +Member organizations that enroll agents gain: + +- **`registered` source** — callers can distinguish your agent from crawl-discovered entries +- **Visibility tiers** — choose `public` (default), `members_only` (visible only to authenticated AgenticAdvertising.org API callers), or `private` (visible only when you are the caller) +- **`member` field populated** — your organization name and slug appear alongside the agent in registry responses +- **Verified badge** — displayed in the public registry UI +- **Storyboard testing access** — run your agent against the AdCP compliance suite + +To enroll as an AgenticAdvertising.org member, visit [agenticadvertising.org/join](https://agenticadvertising.org/join). + +## Getting a non-member agent discovered + +If you are not yet an AgenticAdvertising.org member, the only path into the registry is through a member publisher's `adagents.json`. Ask the publisher to add your agent URL with the appropriate `authorized_for` value. The registry crawler runs on a regular schedule and will pick up the entry on its next pass. + +Self-registration without AgenticAdvertising.org membership is on the roadmap — follow [#3538](https://github.com/adcontextprotocol/adcp/issues/3538) for status. diff --git a/scripts/generate-openapi.ts b/scripts/generate-openapi.ts index ba6f681c7a..dbcdbf5733 100644 --- a/scripts/generate-openapi.ts +++ b/scripts/generate-openapi.ts @@ -89,7 +89,7 @@ const TAG_DESCRIPTIONS: Record = { "Property Resolution": "Resolve publisher domains to their property configurations and authorized agents.", "Agent Discovery": "Browse the federated agent network, search agent inventory profiles, publisher index, and registry statistics.", "Change Feed": "Poll cursor-based registry change events for local sync.", - "Lookups & Authorization": "Look up agents by domain or property, and validate ad-serving authorization.", + "Authorization Lookups": "Look up agents by domain or property, and validate ad-serving authorization.", "Validation Tools": "Validate publisher adagents.json files and generate compliant configurations.", "Search": "Cross-entity search across brands, publishers, agents, and properties.", "Agent Probing": "Connect to live agents and inspect their capabilities, formats, and inventory.", diff --git a/server/src/crawler.ts b/server/src/crawler.ts index 0f0c96b380..8c352d80e6 100644 --- a/server/src/crawler.ts +++ b/server/src/crawler.ts @@ -577,7 +577,7 @@ export class CrawlerService { this.snapshotDb.upsertHealth(agent.url, health, stats), ]); - if (!knownTypes.has(agent.url) && inferredType !== 'unknown') { + if (inferredType !== 'unknown' && inferredType !== knownTypes.get(agent.url)) { await this.federatedIndex.updateAgentMetadata(agent.url, { agent_type: inferredType, protocol: profile.protocol, diff --git a/server/src/routes/registry-api.ts b/server/src/routes/registry-api.ts index 6f7f8d8103..2ba11d4358 100644 --- a/server/src/routes/registry-api.ts +++ b/server/src/routes/registry-api.ts @@ -608,14 +608,14 @@ registry.registerPath({ }, }); -// Lookups & Authorization +// Authorization Lookups registry.registerPath({ method: "get", path: "/api/registry/lookup/domain/{domain}", operationId: "lookupDomain", summary: "Domain lookup", description: "Find all agents authorized for a given publisher domain.", - tags: ["Lookups & Authorization"], + tags: ["Authorization Lookups"], request: { params: z.object({ domain: z.string().openapi({ example: "examplepub.com" }) }) }, responses: { 200: { description: "Domain lookup result", content: { "application/json": { schema: DomainLookupResultSchema } } }, @@ -628,7 +628,7 @@ registry.registerPath({ operationId: "lookupProperty", summary: "Property identifier lookup", description: "Find agents that hold a specific property identifier.", - tags: ["Lookups & Authorization"], + tags: ["Authorization Lookups"], request: { query: z.object({ type: z.string(), value: z.string() }) }, responses: { 200: { description: "Matching agents", content: { "application/json": { schema: z.object({ type: z.string(), value: z.string(), agents: z.array(z.unknown()), count: z.number().int() }) } } }, @@ -641,7 +641,7 @@ registry.registerPath({ operationId: "getAgentDomains", summary: "Agent domain lookup", description: "Get all publisher domains associated with an agent.", - tags: ["Lookups & Authorization"], + tags: ["Authorization Lookups"], request: { params: z.object({ agentUrl: z.string() }) }, responses: { 200: { description: "Domains for the agent", content: { "application/json": { schema: z.object({ agent_url: z.string(), domains: z.array(z.string()), count: z.number().int() }) } } }, @@ -653,8 +653,12 @@ registry.registerPath({ path: "/api/registry/operator", operationId: "lookupOperator", summary: "Operator lookup", - description: "Given a domain, returns the agents this entity operates and which publishers trust them.", - tags: ["Lookups & Authorization"], + description: + "Given a domain, returns the agents this entity operates and which publishers trust them. " + + "Response visibility is caller-dependent: anonymous callers see public agents only; " + + "callers with an AgenticAdvertising.org API-access token additionally see members_only agents; " + + "callers who are the profile owner additionally see private agents.", + tags: ["Authorization Lookups"], request: { query: z.object({ domain: z.string().openapi({ example: "pubmatic.com" }), @@ -672,7 +676,7 @@ registry.registerPath({ operationId: "lookupPublisher", summary: "Publisher lookup", description: "Given a domain, returns the inventory this entity publishes and which agents it authorizes.", - tags: ["Lookups & Authorization"], + tags: ["Authorization Lookups"], request: { query: z.object({ domain: z.string().openapi({ example: "voxmedia.com" }), @@ -691,7 +695,7 @@ registry.registerPath({ summary: "Validate product authorization", description: "Check whether an agent is authorized to sell a product based on its publisher_properties.", - tags: ["Lookups & Authorization"], + tags: ["Authorization Lookups"], request: { body: { content: { @@ -715,7 +719,7 @@ registry.registerPath({ operationId: "expandProductIdentifiers", summary: "Expand product identifiers", description: "Expand publisher_properties selectors into concrete property identifiers for caching.", - tags: ["Lookups & Authorization"], + tags: ["Authorization Lookups"], request: { body: { content: { @@ -753,7 +757,7 @@ registry.registerPath({ operationId: "validatePropertyAuthorization", summary: "Property authorization check", description: "Quick check if a property identifier is authorized for an agent. Optimized for real-time ad request validation.", - tags: ["Lookups & Authorization"], + tags: ["Authorization Lookups"], request: { query: z.object({ agent_url: z.string(), @@ -5160,7 +5164,7 @@ export function createRegistryApiRouter(config: RegistryApiConfig): Router { } }); - // ── Lookups & Authorization ─────────────────────────────────── + // ── Authorization Lookups ───────────────────────────────────── router.get("/registry/operator", optAuth, async (req, res) => { const rawDomain = req.query.domain as string; diff --git a/server/src/schemas/registry.ts b/server/src/schemas/registry.ts index f254256152..b700f6ad0d 100644 --- a/server/src/schemas/registry.ts +++ b/server/src/schemas/registry.ts @@ -353,9 +353,23 @@ export const FederatedAgentWithDetailsSchema = z }) .optional(), added_date: z.string().optional(), - source: z.enum(["registered", "discovered"]).optional(), - member: MemberRefSchema.optional(), - discovered_from: DiscoveredFromSchema.optional(), + source: z + .enum(["registered", "discovered"]) + .optional() + .openapi({ + description: + "How this agent entered the registry. 'registered' means the operator directly enrolled " + + "via AgenticAdvertising.org and has accepted its terms. 'discovered' means the agent was " + + "found in a member publisher's adagents.json and has not explicitly opted in.", + }), + member: MemberRefSchema.optional().openapi({ + description: + "AgenticAdvertising.org member organization that registered this agent. Present only when source='registered'.", + }), + discovered_from: DiscoveredFromSchema.optional().openapi({ + description: + "The member publisher whose adagents.json referenced this agent. Present only when source='discovered'.", + }), health: AgentHealthSchema.optional(), stats: AgentStatsSchema.optional(), capabilities: AgentCapabilitiesSchema.optional(), diff --git a/static/openapi/registry.yaml b/static/openapi/registry.yaml index 5f1b1bddc5..ef7cadb152 100644 --- a/static/openapi/registry.yaml +++ b/static/openapi/registry.yaml @@ -2579,7 +2579,7 @@ paths: summary: Domain lookup description: Find all agents authorized for a given publisher domain. tags: - - Lookups & Authorization + - Authorization Lookups parameters: - schema: type: string @@ -2600,7 +2600,7 @@ paths: summary: Property identifier lookup description: Find agents that hold a specific property identifier. tags: - - Lookups & Authorization + - Authorization Lookups parameters: - schema: type: string @@ -2640,7 +2640,7 @@ paths: summary: Agent domain lookup description: Get all publisher domains associated with an agent. tags: - - Lookups & Authorization + - Authorization Lookups parameters: - schema: type: string @@ -2673,7 +2673,7 @@ paths: summary: Operator lookup description: Given a domain, returns the agents this entity operates and which publishers trust them. tags: - - Lookups & Authorization + - Authorization Lookups parameters: - schema: type: string @@ -2700,7 +2700,7 @@ paths: summary: Publisher lookup description: Given a domain, returns the inventory this entity publishes and which agents it authorizes. tags: - - Lookups & Authorization + - Authorization Lookups parameters: - schema: type: string @@ -2727,7 +2727,7 @@ paths: summary: Validate product authorization description: Check whether an agent is authorized to sell a product based on its publisher_properties. tags: - - Lookups & Authorization + - Authorization Lookups requestBody: content: application/json: @@ -2768,7 +2768,7 @@ paths: summary: Expand product identifiers description: Expand publisher_properties selectors into concrete property identifiers for caching. tags: - - Lookups & Authorization + - Authorization Lookups requestBody: content: application/json: @@ -2834,7 +2834,7 @@ paths: summary: Property authorization check description: Quick check if a property identifier is authorized for an agent. Optimized for real-time ad request validation. tags: - - Lookups & Authorization + - Authorization Lookups parameters: - schema: type: string @@ -6000,7 +6000,7 @@ tags: description: Browse the federated agent network, search agent inventory profiles, publisher index, and registry statistics. - name: Change Feed description: Poll cursor-based registry change events for local sync. - - name: Lookups & Authorization + - name: Authorization Lookups description: Look up agents by domain or property, and validate ad-serving authorization. - name: Validation Tools description: Validate publisher adagents.json files and generate compliant configurations. From 31ece727d5fae0e6304df99470893d73c188714d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Apr 2026 21:55:34 +0000 Subject: [PATCH 2/2] fix(registry): correct source-field scope in registering-an-agent docs source is not present on /operator response (OperatorAgentSchema has no source field); remove the incorrect claim per pre-PR review finding. https://claude.ai/code/session_01LpZQ7dcUTFgreLrUkKsLht --- docs/registry/registering-an-agent.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/registry/registering-an-agent.mdx b/docs/registry/registering-an-agent.mdx index 9f273fde42..64ed989666 100644 --- a/docs/registry/registering-an-agent.mdx +++ b/docs/registry/registering-an-agent.mdx @@ -16,7 +16,7 @@ Agents appear in the AgenticAdvertising.org registry through two paths. The path ## What trust level looks like in API responses -The `source` field is returned on every agent in `GET /api/registry/agents` and on the `operators` list in `GET /api/registry/operator`. +The `source` field is returned on every agent in `GET /api/registry/agents`. ```json Registered agent (member direct) { @@ -54,4 +54,4 @@ To enroll as an AgenticAdvertising.org member, visit [agenticadvertising.org/joi If you are not yet an AgenticAdvertising.org member, the only path into the registry is through a member publisher's `adagents.json`. Ask the publisher to add your agent URL with the appropriate `authorized_for` value. The registry crawler runs on a regular schedule and will pick up the entry on its next pass. -Self-registration without AgenticAdvertising.org membership is on the roadmap — follow [#3538](https://github.com/adcontextprotocol/adcp/issues/3538) for status. +Self-registration without AgenticAdvertising.org membership is on the roadmap. A separate tracking issue will be filed once the trust-tier model for self-attested agents is decided.