Skip to content

feat(negotiation): add first-class buyer and seller proposal APIs - #37

Open
garvitkaushik-123 wants to merge 2 commits into
adcontextprotocol:mainfrom
garvitkaushik-123:feat/negotiation-proposal-apis
Open

feat(negotiation): add first-class buyer and seller proposal APIs#37
garvitkaushik-123 wants to merge 2 commits into
adcontextprotocol:mainfrom
garvitkaushik-123:feat/negotiation-proposal-apis

Conversation

@garvitkaushik-123

Copy link
Copy Markdown

Summary

Implements the foundation for #36 — first-class proposal negotiation types for the refine_proposals tool surface introduced in AdCP 3.2.

This covers the three modules referenced in the issue's acceptance criteria:

Client (adcp module)

  • Sealed RefinementResult with four outcome variants (Revised, Partial, Finalized, Unable) supporting Java 21 exhaustive pattern matching and Jackson polymorphic deserialization on the outcome discriminator
  • RefineProposalsRequest builder enforcing batch size ceilings, unique proposal IDs, finalize-only homogeneity, and idempotency key format — all validated before transport
  • Typed constraint records for the three new 3.2 constraints: CpmConstraint (max CPM + currency), ImpressionsConstraint (minimum summed impressions), FlightConstraint (start/end bounds with the "asap never satisfies start" rule noted in the spec)
  • TermsDigest — self-contained RFC 8785 JCS canonicalization + SHA-256 digest computation and verification. Recomputes and compares rather than trusting the string, per the 3.2 normative spec. Also provides areDistinct() for alternative distinctness checks
  • ResponseVerifier checking result ordering against the request, lineage (parent_proposal_id == source_proposal_id), finalize atomicity, outcome-specific structural constraints (status/expires_at/reason/notes), partial invariant (unsatisfied_constraints must be present), unique proposal IDs, and digest integrity
  • RefinementCapability with the post-rename product_changes dimension key
  • UnsupportedRefinementDetails for typed UNSUPPORTED_FEATURE error recovery
  • AdcpClient.refineProposals() convenience method delegating to callNamedTool

Server (adcp-server module)

  • ProposalHandler interface with capability declaration, optional preflight hook for cross-entry validation, and refine() callback. Framework does batch validation; commercial decisions stay in the application callback

Testing (adcp-testing module)

  • NegotiationFixtures with factory methods for single revise, batch finalize, draft/committed proposals, constraint objects, and JSON response fragments for revised and unable outcomes

Design decisions

  • unsatisfied_constraints is List<String> (open strings), not an enum — per the 3.2 delta comment
  • Reason code precedence rule is documented but not enforced at the type level (it's a server-side business rule)
  • JCS canonicalization is self-contained to avoid a mandatory runtime dep on org.webpki.jcs for the common case. The risky JCS number formatting follows ES2015 rules
  • ProposalRefinement factory methods cover the four common patterns (revise with instructions, revise with criteria, finalize, cancel)

Not in this PR

  • Reactive (Reactor/Mutiny) and Kotlin coroutine surfaces — these will mirror the same types once the sync API shape stabilizes
  • Integration tests against a live mock server (will follow in a separate PR extending adcp-testing)
  • Wiring ProposalHandler into AdcpServerBuilder tool registration (needs the codegen track to emit the input schema)

Test plan

  • RefineProposalsRequestTest — builder validation: valid requests, null/short idempotency keys, empty refinements, mixed finalize/revise rejection, duplicate proposal IDs, batch size ceiling, immutable list, cancellation refinements
  • RefinementResultTest — sealed interface permits, Jackson round-trip for all four outcomes, pattern matching exhaustiveness
  • TermsDigestTest — sha256 prefix, determinism, key-order independence, verify/tamper detection, null/wrong-prefix rejection, distinctness, JCS canonicalization of nested objects/strings/numbers/booleans/nulls
  • ResponseVerifierTest — valid response passes, count mismatch, ordering mismatch, mixed finalize, missing lineage, wrong proposal_status for finalized, async skip, digest mismatch detection
  • ConstraintsTest — validation for all three constraint types, rejection of invalid inputs, Jackson round-trip serialization
  • Full ./gradlew build passes (all 55 tasks, all existing tests still green)

Closes #36 (partial — see "Not in this PR" above; reactive/Kotlin surfaces and integration tests tracked separately)

Introduces the `negotiation` package across adcp, adcp-server, and
adcp-testing modules for the refine_proposals tool surface (AdCP 3.2).

Client (adcp module):
- Sealed RefinementResult with four outcomes (revised/partial/finalized/unable)
  supporting exhaustive pattern matching
- Capability-aware RefineProposalsRequest builder enforcing batch size
  ceilings, unique proposal IDs, and finalize-only homogeneity
- Three typed constraints from the 3.2 delta: CpmConstraint,
  ImpressionsConstraint, FlightConstraint
- TermsDigest utility implementing RFC 8785 JCS canonicalization for
  SHA-256 digest computation and verification
- ResponseVerifier checking result ordering, lineage (parent_proposal_id),
  finalize atomicity, outcome structural constraints, partial invariant,
  unique alternatives, and digest integrity
- AdcpClient.refineProposals() convenience method
- UnsupportedRefinementDetails for typed UNSUPPORTED_FEATURE error recovery
- RefinementCapability with product_changes dimension key (post-rename)

Server (adcp-server module):
- ProposalHandler interface with capability declaration, batch preflight
  hook, and refine callback — commercial decisions stay in the application

Testing (adcp-testing module):
- NegotiationFixtures with factory methods for common scenarios: single
  revise, batch finalize, constraint objects, draft/committed proposals

Covers the unsatisfied_constraints-is-open-strings design, reason code
precedence (constraint_unsatisfiable > commercially_declined), lineage
verification, and the hold_unavailable/batch_aborted reason codes from
the post-red-team protocol delta.

Ref: adcontextprotocol#36
@aao-ipr-bot

aao-ipr-bot Bot commented Aug 15, 2026

Copy link
Copy Markdown

⚠️ Argus review could not complete

The automated review encountered an issue (possibly reached max turns, timed out, or failed to post the final gh pr review). A human reviewer should take this PR.

View workflow run

This is an automated message from the Argus AI review workflow.

- TermsDigest: remove unused MAPPER field and three unused imports
  (JsonProcessingException, Iterator, Map)
- TermsDigest: add @nullable to verify() digest parameter — the method
  handles null but the @NullMarked package default says non-null
- TermsDigest: fix JCS exponent formatting — strip ".0" before 'e' so
  1.0E-7 becomes "1e-7" per ES2015/RFC 8785 (was producing "1.0e-7")
- TermsDigest: replace per-character OutputStreamWriter allocation in
  writeCanonicalString with direct String.valueOf(c).getBytes()
- RefineProposalsRequest: remove unused Collections import and dead
  supportedDimensions builder field (stored but never validated)
- AdcpClient: fix import ordering — negotiation imports grouped with
  other org.adcontextprotocol imports, not after slf4j
- Add serialization round-trip test for RefinementResult
- Add test coverage for JCS exponent edge cases
@aao-ipr-bot

aao-ipr-bot Bot commented Aug 15, 2026

Copy link
Copy Markdown

⚠️ Argus review could not complete

The automated review encountered an issue (possibly reached max turns, timed out, or failed to post the final gh pr review). A human reviewer should take this PR.

View workflow run

This is an automated message from the Argus AI review workflow.

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.

feat(negotiation): add first-class buyer and seller proposal APIs

1 participant