Skip to content

feat(openapi): describe the property-catalog fact-contribution endpoints#5787

Merged
bokelley merged 1 commit into
mainfrom
feat/openapi-catalog-fact-endpoints
Jul 1, 2026
Merged

feat(openapi): describe the property-catalog fact-contribution endpoints#5787
bokelley merged 1 commit into
mainfrom
feat/openapi-catalog-fact-endpoints

Conversation

@bokelley

@bokelley bokelley commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

The prerequisite that makes the SDK "send us facts" surface typeable — the "canonical registry spec" that adcontextprotocol/adcp-client#2317 explicitly defers to.

Problem

The catalog fact endpoints (POST /api/registry/resolve, /catalog/disputes) are live and serve 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 primary fact-contribution surface (reportIdentifiers/disputeFact in #5782) couldn't be typed at all.

Change (documentation of live endpoints — no behavior change)

  • 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:
    • resolveIdentifiersPOST /api/registry/resolve (the primary fact funnel)
    • fileCatalogDisputePOST /api/registry/catalog/disputes
    • getCatalogDisputeGET /api/registry/catalog/disputes/{id}
  • Schemas mirror the live Zod validators + result types exactly: the provenance enum, identifiers[1..10000] cap, ResolveResponse.summary's five fields (incl not_found), and the dispute TriageResult ({dispute_id, action_taken, reason}).
  • property_rid documented as a non-authoritative join/match handle, never an authorization credential (the fix(registry): enforce identity-not-authorization on property-save (#5749 gap #2) #5750 identity-not-authorization lesson).
  • Wired into scripts/generate-openapi.ts + a "Property Catalog" tag; regenerated registry.yaml.

Where it fits

Read-side OpenAPI (browse/sync) is a fast follow-on. Validated: npm run build:openapi regenerates cleanly; typecheck clean; unit suite green.

🤖 Generated with Claude Code

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>
@mintlify

mintlify Bot commented Jul 1, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
adcp 🟢 Ready View Preview Jul 1, 2026, 1:13 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@aao-release-bot aao-release-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accurate documentation of three already-live endpoints — the schemas mirror the runtime, which is the whole job of an OpenAPI-registration PR. Approving: this makes the fact-contribution surface typeable for SDK codegen without touching a single wire behavior.

Things I checked

  • Request validators mirror the routes exactly. ResolveRequest (identifiers.min(1).max(10000), the 8-value provenance enum, mode default resolve) matches catalog-api.ts:58-70; CatalogDisputeRequest (claim 10–2000, evidence ≤5000 optional) matches catalog-api.ts:72-78.
  • Response shapes mirror the result types. ResolveResponse/ResolvedEntry — nullable property_rid/source, status enum existing|created|excluded, the five-field summary including not_found — matches catalog-db.ts:72-90 and the return at :298-308. CatalogDisputeTriageResult's action_taken enum matches catalog-governance.ts:29-33.
  • Auth annotations match the handlers. Resolve documents security + a 401 and requires req.user only in resolve mode (catalog-api.ts:169-171); disputes POST requires auth (catalog-api.ts:533); GET dispute is correctly annotation-free because the handler has no gate.
  • No changeset required. static/openapi/** and scripts/generate-openapi.ts are deliberately outside the release/versioning path (.github/workflows/release.yml:65 scopes it to static/schemas/source/, static/compliance/source/, and named build scripts). This is the registry REST surface, not the AdCP wire schemas — static/schemas/source/** is untouched.
  • property_rid framing is load-bearing and consistent. Documented as a non-authoritative join/match handle, never an authorization credential, in both the field and operation descriptions — the #5750 identity-not-authorization lesson, correctly carried.

Follow-ups (non-blocking — file as issues)

  • Public GET on /catalog/disputes/{id} returns reporter PII. The handler does SELECT * and returns the row verbatim (catalog-api.ts:559-570), so reported_by_email ships on an unauthenticated read. CatalogDisputeRecord doesn't document that field, so .passthrough() hides the leak from the contract rather than fixing it. Pre-existing route behavior, not introduced here — so not a block on a docs PR — but this is the moment the endpoint becomes a published surface. Route should stop returning reporter email on the public path; keep it out of the documented schema either way. Worth its own issue.
  • CatalogDisputeRecord under-specifies the read. The row carries stable columns (resolution, resolved_by, resolved_at, updated_at; catalog-disputes-db.ts:8-23) that .passthrough() leaves untyped for SDK consumers. Pinning them (minus PII, per above) would give the disputes-read surface a real generated type.
  • summary.resolved double-counts created. It's computed as existing + created (catalog-db.ts:296-306), so an SDK author summing resolved + created + excluded against total will be off. One line on the field description prevents the misuse.

Minor nits (non-blocking)

  1. status example 'suspended' can never occur. DisputeRecordSchema.status uses example: 'suspended', but a dispute status is open|investigating|resolved|rejected|escalated (catalog-disputes-db.ts:26), and fileDispute sets it to investigating (catalog-governance.ts:60/71/82). link_suspended is an action_taken value on the triage result — the example crossed the two surfaces. Change it to investigating.

LGTM. Follow-ups noted below — the PII one deserves a separate fix, not a comment on this PR.

@bokelley bokelley merged commit 72e4088 into main Jul 1, 2026
15 checks passed
@bokelley bokelley deleted the feat/openapi-catalog-fact-endpoints branch July 1, 2026 13:30
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant