Conversation
| "update": "optional", | ||
| "complete": "optional" | ||
| }, | ||
| "ucp_response": "omit" |
There was a problem hiding this comment.
Can you comment on why we don’t reflect these back in the response? I am not going to die on this hill, but as I mentioned in our initial discussion, I find it odd that this is one of the few platform-provided fields they would need to keep the state of themselves, since they can’t read the current values from a get_checkout call.
There was a problem hiding this comment.
You found the honeypot.
I don't have a strong philosophical objection to removing omit annotation on response, exactly for the reasons you mentioned. However, if you peek two lines above, you'll see that we have same pattern on context object and hence why I replicated the pattern here. I think we can make the exact same argument for context, and perhaps we should. Let's discuss on the TC call tomorrow.
raginpirate
left a comment
There was a problem hiding this comment.
Two strong opinions I'll throw your way! Sadly I missed the breakout on this due to flying, so lmk if there was already consensus on these topics; if there was, at least we can document why my thoughts are wrong 😄
|
The signals-as-platform-attestations model is clean. One extension pattern worth considering: cryptographic third-party attestation signals. The current examples ( These aren't relayed buyer claims. They're ECDSA-signed boolean results from an independent verifier, checkable against a published JWKS endpoint. They satisfy the "MUST reflect direct observations" requirement because they reflect the verifier's direct observation of chain state at a specific block height. A possible shape using the extension mechanism: {
"signals": {
"buyer_ip": "203.0.113.42",
"ext.attestation": {
"provider_jwks": "https://provider.example/.well-known/jwks.json",
"pass": true,
"attested_at": "2026-02-27T12:00:00Z",
"signature": "base64-p1363..."
}
}
}The interesting property: unlike platform-observed signals, cryptographic attestation signals are self-verifying. The business validates the signature locally without trusting the platform or calling back to the attestation provider. This actually strengthens the trust model — the business gets a signal it can independently audit. We operate a UCP-compatible attestation service that issues ECDSA P-256 signed verification results for on-chain conditions across 31 chains. The |
|
@raginpirate extracting from comment thread so it's more prominent...
Yes, the extensions are namespaced (dev.acme.risk), but that doesn't automatically propagate to the keys it registers into the shared signals object. The "out-of-band parties can agree on names" is true for bilateral relationships, but UCP is many-to-many — a platform serves multiple businesses, each with different extension combinations. To your point though, why here and not for other extensions? Most UCP extension points are structurally collision-safe: capability registries key by reverse-domain name, arrays (messages, discounts) keep items independent. Could we relax to a SHOULD? We could, but even seemingly safe shared names have hidden footguns: one extension declares I think leaning into a MUST is the right contract here, and now that I've typed it out, I think we should make it schema enforceable — require reverse-dns on propertyNames, same as we do for handlers and services. A flat map with propertyNames validation gives us both collision safety with direct key access, and every key carries provenance: you know exactly who defined it via key name, and we explicitly prevent collisions. In this setup, See: f1baa9a |
Clean up all risk_signals language from the earlier draft that was superseded by the signals proposal (issue #153).
Platforms mediate buyer interaction, making them the sole party
able to observe the transaction environment (IP, user agent, etc).
Businesses need this data for authorization decisions, rate limiting,
and abuse prevention — but have no direct access to it.
- Signals are platform attestations: values MUST reflect direct
observations, not relayed buyer claims
- Signals serve authorization, rate limiting, and abuse prevention
across cart and checkout
- Signal feedback via info messages with well-known codes: risk, abuse
- Proprietary signals can use reverse-domain naming (com.example.score)
Unlike other UCP extension points — capability registries key by reverse-domain name, arrays keep items independent — signals is a shared flat map where multiple independent extensions contribute keys into the same object. Without naming discipline, collisions are inevitable: two extensions both defining "device_id" or "ja4" with different types or semantics produces silently unsatisfiable allOf composition. Reverse-domain naming makes every key self-describing: you know who defined it, what schema governs it, and collisions are structurally impossible. This is the same coordination-free mechanism UCP already uses for capabilities, services, and payment handlers. This commit enforces the MUST at the schema level via propertyNames (not just prose), renames well-known signals into the dev.ucp namespace (buyer_ip → dev.ucp.buyer_ip, user_agent → dev.ucp.user_agent), and updates all docs and examples to match.
The original spec language required signals to be "direct platform observations," which excluded a valuable class of signals: independently verifiable third-party attestations (e.g., cryptographically signed results from an external verifier). These aren't buyer-asserted claims — the business can validate the signature against the provider's published key set without trusting the platform at all.
|
@douglasborthwick-crypto great call out, agree and updated spec language: 7d50b2c |
|
Thanks Ilya — the language shift to "independently verifiable third-party attestations" is the right framing. The trust guarantee comes from cryptographic verifiability, not from who observed the data. Two notes on the example shape in the updated spec: 1. The 2. The signal needs the full signed payload to be self-verifying For the business to actually verify the signature locally, the signal must include the complete signed payload — not just Here's what a verifiable attestation signal looks like with reverse-domain naming: "com.insumermodel.attestation": {
"provider_jwks": "https://api.insumermodel.com/v1/jwks",
"kid": "insumer-attest-v1",
"id": "ATST-A7C3E",
"pass": true,
"results": ["...per-condition evaluations with conditionHash for tamper evidence..."],
"attestedAt": "2026-03-03T12:00:00Z",
"sig": "MEYCIQDx...base64..."
}The business verifies by: fetching JWKS from Happy to contribute an extension schema for this once the PR lands. |
raginpirate
left a comment
There was a problem hiding this comment.
Thanks for the POV, I think thats fair although I still would have loved to allow collisions purposefully to re-use simple integrations with simple field names. But I get your point that the typing problem can become hard if integrators overload with poor comparison on the base types (like two string ids).
I also flagged internally for a quick review on those code names to make sure we are not giving away too much info for bad actors with codes like "risk" and "abuse".
Updated shape includes kid (RFC 7517 key selection/rotation), the complete signed payload (opaque, extension-defined), and sig over that payload. The business can now verify end-to-end: fetch JWKS, select key by kid, verify sig over payload.
|
@douglasborthwick-crypto good call, restructured the example to carry the full signed payload + sig, so the shape is self-verifiable. The goal here is to provide an illustrative example, the actual payload is specific to the provider and, as you outlined, is deferred to the extension that provides the signal+attestation. |
Super-linter summary
All files and directories linted successfully For more information, see the Powered by Super-linter |
Replace the risk/abuse code taxonomy with a single `signal` code. The path field already identifies what's being requested, and the signal is self-describing. Removes info-only language to allow and support different business strategies for information vs required signals.
Super-linter summary
All files and directories linted successfully For more information, see the Powered by Super-linter |
Platforms mediate buyer interaction, making them the sole party able to observe the transaction environment (IP, user agent, etc). Businesses need this data for authorization decisions, rate limiting, and abuse prevention — but have no direct access to it. This PR defines
signalscontract that allows businesses to dynamically notify platforms of requested signals (via info messages on the response) and for platforms to provide data on the request(s).risk,abusecodes{ "id": "cs_abc", "status": "ready_for_complete", "line_items": [...], "messages": [ { "type": "info", "code": "risk", "path": "$.signals.user_agent", "content": "User agent improves authorization confidence for high-value orders" } ] }Closes #153.
Checklist