docs(specs): RFC for the SDK fact-contribution surface#5782
Open
bokelley wants to merge 1 commit into
Open
Conversation
The server already exposes the full "send us facts" surface (POST /api/registry/resolve with provenance, /catalog/disputes, and the #5752 claim/verify-origin), but @adcp/sdk's RegistryClient surfaces only saveProperty. This RFC defines the client surface — the same shape in @adcp/client (TS) and adcp (Python) — so participants contribute facts as a byproduct of resolving their own identifier lists. - reportIdentifiers() (wraps /resolve) as the primary funnel: resolving IS contributing; mode is required and the method is named for the write so a lookup-looking call can't silently create catalog rows; provenance required in resolve mode; property_rid documented as a non-authorization handle. - disputeFact() / getDispute(), browseCatalog() / syncCatalog() (two distinct pagination cursors), claimDomain() / verifyOrigin() (origin-rooted authority), saveProperty() (exists, identity-only). - Flat surface (no registry.facts.* sub-client) for clean TS/Python parity; trust boundary named in methods + docs. Spec once + shared conformance vector, given the adcp-client#2301 resolver divergence. - Error contract grounded in the live routes (401 all writes, 403 claimDomain org membership, 429 only on claim/verify/save — /resolve has no limiter, verifyOrigin transient is the retryable case). Reviewed by the agentic-product-architect against the live endpoints; it caught a fabricated 429/tiering contract, a dropped summary.not_found field, and the wrong dispute response shape — all corrected. Client-side counterpart to the catalog-graduation RFC; no wire/schema change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Draft RFC for the client-side fact-contribution surface — no wire or schema change, so this ships on the strength of whether it describes the live routes honestly. It does.
Things I checked
/resolveauth claim is accurate.catalog-api.ts:169gates auth onmode === 'resolve' && !req.user→ 401;lookupneeds no auth. The RFC's 401-table matches the route, not the wish.- "
/resolvehas no rate-limiter today" is correct. The/resolvehandler mounts nosaveMiddleware; the claim/verify/save family does. The RFC explicitly tells both SDKs not to build a 429/tiering contract against/resolve— the right call, because the earlier draft's fabricated429was the exact bug the agentic-product-architect pass caught. browseCatalogshape matches.GET /catalogreturns{ entries, total, next_cursor }(catalog-api.ts:220-224) — the opaque-cursor mechanism, distinct fromsyncCatalog's timestamp watermark. The RFC keeps them as two separate pagination surfaces instead of conflating them.- Referenced specs resolve.
property-catalog-graduation.md,property-definition-alignment.md,registry-authorization-model.mdall exist underspecs/. No dangling links. - The core design move is the right one. Naming the write
reportIdentifiers()instead ofresolve(), makingmoderequired, and structurally requiringprovenancein resolve mode closes the footgun the RFC names itself: the write path looks like a lookup. Fail-closed beats fail-open on a method that silently creates catalog rows and demand signal.
Follow-ups (non-blocking — file as issues)
- The RFC leans hard on a shared conformance vector asserting the full wire shape (
summary.not_found, disputeaction_taken, theverifyOriginreasonunion) to prevent the adcp-client#2301 resolver drift from freezing into both SDKs. That vector is the load-bearing artifact here and it lives in another repo. Track it as a hard dependency of the implementation PRs, not a nice-to-have — a spec-once that ships twice without the vector is just two drifts with a shared parent. resolve()as a "documented alias" forreportIdentifiers()reopens the footgun by name. If it ships, it should carry the same requiredmode/provenancediscriminated union — worth stating in the implementation spec so the alias can't become the soft-write escape hatch.
Minor nits (non-blocking)
crawlprovenance exclusion. The RFC notescrawlis server-internal and not offered to callers. Good — but the SDK's localprovenance.typevalidation (the 400-preempt list) should enumerate the seven allowed values and rejectcrawlexplicitly, so a caller copying a server-side value gets a clean local error instead of a round-trip.
Grounded in the actual routes, and honest about what's built versus what's still the SDK team's work. Approving on the strength of the endpoint-accuracy pass plus the closed write-path footgun.
LGTM. Follow-ups noted below.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
bokelley
added a commit
that referenced
this pull request
Jul 1, 2026
…nts (#5787) The catalog fact surface (POST /api/registry/resolve, /catalog/disputes) is live and serves real traffic, but catalog-api.ts registered zero OpenAPI paths — the whole router was absent from static/openapi/registry.yaml. Since SDK clients generate their types from the published OpenAPI (adcp-client's types.generated.ts sources https://agenticadvertising.org/openapi/registry.yaml), the "send us facts" surface could not be typed. This makes it part of the contract. - New server/src/schemas/catalog-openapi.ts (mirrors the member-agents-openapi.ts pattern — standalone so the generator imports it without route-factory deps). Registers: resolveIdentifiers (POST /api/registry/resolve), fileCatalogDispute (POST /api/registry/catalog/disputes), getCatalogDispute (GET .../disputes/{id}). - Schemas mirror the Zod validators + result types in catalog-api.ts / catalog-db.ts / catalog-governance.ts exactly: the provenance enum, the identifiers[1..10000] cap, ResolveResponse.summary's five fields (incl not_found), and the dispute TriageResult ({dispute_id, action_taken, reason}). - property_rid is documented as a non-authoritative join/match handle, never an authorization credential (mirrors the #5750 identity-not-authorization lesson). - Wired into scripts/generate-openapi.ts + a "Property Catalog" tag; regenerated static/openapi/registry.yaml. Prerequisite for the SDK fact-contribution surface (specs/sdk-fact-contribution.md, #5782): once this publishes, @adcp/client and adcp regenerate types and add the reportIdentifiers()/disputeFact() methods. Read-side (browse/sync) OpenAPI is a fast follow-on. No behavior change — documentation of live endpoints only. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
Jul 1, 2026
…5790) Completes the property-catalog OpenAPI surface started in #5787. The catalog read/consume endpoints are live but were absent from static/openapi/registry.yaml, so the SDK's read-side (browseCatalog/syncCatalog — the second half of the resolve→sync→build-lists loop from RFC #5782) couldn't be typed. - browseCatalog — GET /api/registry/catalog: filtered, cursor-paginated browse; each entry carries the property's identifiers. - syncCatalog — GET /api/registry/catalog/sync: delta since a `server_timestamp` watermark (capped 10,000/page), distinct from browse's opaque cursor. Schemas mirror the route handlers + catalog-db result types (browse entries carry identifiers[]; sync entries are raw CatalogProperty with timestamps). property_rid documented as a non-authoritative handle, consistent with #5787. Once published, adcp-client regenerates types and adds browseCatalog()/ syncCatalog() alongside the shipped resolveIdentifiers/fileCatalogDispute/ claim/verify surface (@adcp/sdk 9.7.0). No behavior change — documentation of live endpoints only. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
How participants send us facts — the client-side counterpart to the catalog-graduation RFC. Follows the property-alignment / bind-on-verify work (#5750, #5752, #5761).
The gap
The server already exposes the whole fact surface —
POST /api/registry/resolve(identifiers + provenance →property_rids),POST /api/registry/catalog/disputes, and theclaim/verify-originbind-on-verify endpoints. But@adcp/sdk'sRegistryClientsurfaces onlysaveProperty. This RFC defines the client surface, identical in TS (@adcp/client) and Python (adcp).Key idea: resolving is contributing
A buyer resolving its campaign list to get
property_rids for targeting is simultaneously asserting "these identifiers exist and are demanded, fromagency_allowlist." Contribution is a byproduct of the caller's own workflow;provenanceis the trust envelope. The footgun — the write path looks like a lookup — is closed by naming the methodreportIdentifiers()(notresolve) and makingmodea required argument.Surface (flat, for TS/Python parity)
reportIdentifiers(),disputeFact()/getDispute()browseCatalog()(cursor),syncCatalog(since)(timestamp)claimDomain()/verifyOrigin(),saveProperty()(exists)Principles: provenance structurally required on writes; the write is never implicit;
property_riddocumented as a non-authorization handle; spec-once + a shared conformance vector (given the TS/Python divergence in adcontextprotocol/adcp-client#2301).Review
Reviewed by the agentic-product-architect against the live endpoints. It caught a fabricated
429/tiering contract (/resolvehas no limiter), a dropped requiredsummary.not_found, and the wrongdisputeresponse shape ({dispute_id, action_taken, reason}) — all corrected. Error contract now matches the routes (401 all writes, 403claimDomainorg membership, 429 only on claim/verify/save,verifyOrigintransientretryable).Proposal for WG/SDK-team discussion — no wire/schema change. Implementation lands in the separate
adcp-client/adcp-client-pythonrepos.🤖 Generated with Claude Code