Skip to content

Commit 7fcadab

Browse files
committed
feat: wire real offline schema validation into ucp-validate
Fills the gap noted in the understanding doc: ucp-validate now validates the profile against the official UCP schema (no network at validation time). - Vendor the official v2026-04-08 profile schema tree (8 files, fetched transitively via $ref) into refs/ucp-schema/2026-04-08/, and un-ignore that subtree in .gitignore (the rest of refs/ stays ignored). - validate_ucp.py: load the vendored schemas into a referencing Registry keyed by file path (stripping $id, since upstream $refs resolve by path, not $id) and validate the profile with jsonschema. Graceful SKIP when jsonschema or the schemas are unavailable; schema failures are ERROR-level (CONDITIONAL PASS), not CRITICAL. - Re-add jsonschema to requirements.txt (now genuinely used). - Add a unit test (our generated/fixture profiles conform; a broken profile is rejected). Update SKILL.md, check-matrix.md, README, and the understanding doc to reflect the closed gap. https://claude.ai/code/session_01BWhuWLihyaYzRx8pk7GA7V
1 parent 1d1243b commit 7fcadab

17 files changed

Lines changed: 801 additions & 7 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ store/
88
dist/
99
build/
1010
.DS_Store
11-
refs/
11+
# refs/ holds locally-cloned official tools (ignored), except the vendored
12+
# schema bundle that ucp-validate needs at runtime.
13+
refs/*
14+
!refs/ucp-schema/

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ also backs the Codex plugin (`.codex-plugin/plugin.json`).
105105
| OpenAI ACP feed export | Implemented from normalized catalog JSON |
106106
| UCP checkout server generation | Implemented for sandbox Python/FastAPI |
107107
| Runtime validation gate | Implemented for profile, catalog, checkout create/retrieve/update/cancel |
108+
| Offline official profile-schema validation | Implemented against vendored UCP `2026-04-08` schemas (jsonschema) |
108109
| Full official UCP conformance testing | Still delegated to official tools |
109110

110111
## Tested Against Real Sites
@@ -125,6 +126,7 @@ for deeper conformance checks:
125126
| Layer | Tool | Source |
126127
| --- | --- | --- |
127128
| Profile structure | `validate_ucp.py` | Required fields, capability basics, URL reachability |
129+
| Offline profile schema | `validate_ucp.py` + `refs/ucp-schema/` | Vendored official UCP profile schema (`2026-04-08`) via jsonschema |
128130
| Runtime catalog | `validate_ucp.py` | Search and lookup endpoint preflight |
129131
| Runtime checkout | `validate_ucp.py` | Create, retrieve, update, cancel, totals rules |
130132
| Full UCP schema validation | [`ucp-schema`](https://github.com/Universal-Commerce-Protocol/ucp-schema) | Official Rust CLI |

docs/UNDERSTANDING.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,16 @@ AGENTS.md 的指令是"**选匹配目标的最小路径**"——
230230
| OpenAI ACP | developers.openai.com/commerce(含 `specs/api/products`| ACP feed 字段标准 |
231231
| 示例产物 | `examples/glossier/*` | 一份端到端参考输出 |
232232

233-
### 8.3 一个已知缺口(改动切入点)
234-
项目最该依赖的参考(官方 JSON Schema)目前**没有被真正接进来**
235-
`requirements.txt` 装了 `jsonschema` 却无任何脚本 `import` 它,docstring 声称
236-
"validates against official schema" 但代码里只有手写字段检查。这既是「声明≠实现」
237-
的 bug,也是未来把官方 schema 接入 `validate` 的天然切入点。
233+
### 8.3 已闭合的缺口:官方 schema 已真正接入
234+
> 历史:曾经 `requirements.txt` 装了 `jsonschema` 却无人 import,docstring 谎称
235+
> "validates against official schema"。这是「声明≠实现」的 bug。
236+
237+
现在已修复并**真正实现**:官方 `v2026-04-08` 的 profile schema 树(共 8 个文件,
238+
沿 `$ref` 传递抓取)已 vendored 到 `refs/ucp-schema/2026-04-08/``ucp-validate`
239+
`jsonschema` + `referencing`**离线**校验(无 `jsonschema` 时优雅 SKIP)。
240+
关键细节:上游 `$ref`**文件路径**(相对 `source/`)解析而非按 `$id`,所以
241+
加载时按路径建 registry 并剥掉 `$id`。我们自己生成的 profile(REST/MCP)实测
242+
**0 error** 通过官方 schema,印证了生成端的契约正确性。
238243

239244
---
240245

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://ucp.dev/schemas/discovery/profile.json",
4+
"title": "UCP Discovery Profile",
5+
"description": "Schema for UCP discovery profiles. Business profiles are hosted at /.well-known/ucp; platform profiles are hosted at URIs advertised in request headers.",
6+
7+
"anyOf": [
8+
{ "$ref": "#/$defs/platform_profile" },
9+
{ "$ref": "#/$defs/business_profile" }
10+
],
11+
12+
"$defs": {
13+
"signing_key": {
14+
"type": "object",
15+
"required": ["kid", "kty"],
16+
"description": "Public key for signature verification in JWK format.",
17+
"properties": {
18+
"kid": {
19+
"type": "string",
20+
"description": "Key ID. Referenced in signature headers to identify which key to use for verification."
21+
},
22+
"kty": {
23+
"type": "string",
24+
"description": "Key type (e.g., 'EC', 'RSA')."
25+
},
26+
"crv": {
27+
"type": "string",
28+
"description": "Curve name for EC keys (e.g., 'P-256')."
29+
},
30+
"x": {
31+
"type": "string",
32+
"description": "X coordinate for EC public keys (base64url encoded)."
33+
},
34+
"y": {
35+
"type": "string",
36+
"description": "Y coordinate for EC public keys (base64url encoded)."
37+
},
38+
"n": {
39+
"type": "string",
40+
"description": "Modulus for RSA public keys (base64url encoded)."
41+
},
42+
"e": {
43+
"type": "string",
44+
"description": "Exponent for RSA public keys (base64url encoded)."
45+
},
46+
"use": {
47+
"type": "string",
48+
"enum": ["sig", "enc"],
49+
"description": "Key usage. Should be 'sig' for signing keys."
50+
},
51+
"alg": {
52+
"type": "string",
53+
"description": "Algorithm (e.g., 'ES256', 'RS256')."
54+
}
55+
}
56+
},
57+
58+
"base": {
59+
"description": "Base discovery profile with shared properties for all profile types.",
60+
"type": "object",
61+
"additionalProperties": true,
62+
"required": ["ucp"],
63+
"properties": {
64+
"ucp": {
65+
"$ref": "../schemas/ucp.json#/$defs/base"
66+
},
67+
"signing_keys": {
68+
"type": "array",
69+
"description": "Public keys for signature verification (JWK format). Used to verify signed responses, webhooks, and other authenticated messages from this party.",
70+
"items": {
71+
"$ref": "#/$defs/signing_key"
72+
}
73+
}
74+
}
75+
},
76+
77+
"platform_profile": {
78+
"title": "UCP Platform Discovery Profile",
79+
"description": "Full discovery profile for platforms. Exposes complete service, capability, and payment handler registries.",
80+
"allOf": [
81+
{ "$ref": "#/$defs/base" },
82+
{
83+
"properties": {
84+
"ucp": {
85+
"$ref": "../schemas/ucp.json#/$defs/platform_schema"
86+
}
87+
}
88+
}
89+
]
90+
},
91+
92+
"business_profile": {
93+
"title": "UCP Business Discovery Profile",
94+
"description": "Discovery profile for businesses/merchants. Subset of platform profile with business-specific configuration.",
95+
"allOf": [
96+
{ "$ref": "#/$defs/base" },
97+
{
98+
"properties": {
99+
"ucp": {
100+
"$ref": "../schemas/ucp.json#/$defs/business_schema"
101+
}
102+
}
103+
}
104+
]
105+
}
106+
}
107+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://ucp.dev/schemas/capability.json",
4+
"title": "UCP Capability",
5+
"description": "Schema for UCP capabilities and extensions. Extensions are capabilities with an 'extends' field. Uses reverse-domain naming for governance.",
6+
7+
"$defs": {
8+
"base": {
9+
"allOf": [
10+
{ "$ref": "ucp.json#/$defs/entity" },
11+
{
12+
"type": "object",
13+
"properties": {
14+
"extends": {
15+
"oneOf": [
16+
{
17+
"type": "string",
18+
"pattern": "^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$"
19+
},
20+
{
21+
"type": "array",
22+
"items": {
23+
"type": "string",
24+
"pattern": "^[a-z][a-z0-9]*(?:\\.[a-z][a-z0-9_]*)+$"
25+
},
26+
"minItems": 1
27+
}
28+
],
29+
"description": "Parent capability(s) this extends. Present for extensions, absent for root capabilities. Use array for multi-parent extensions."
30+
}
31+
}
32+
}
33+
]
34+
},
35+
36+
"platform_schema": {
37+
"title": "Capability (Platform Schema)",
38+
"description": "Full capability declaration for platform-level discovery. Includes spec/schema URLs for agent fetching.",
39+
"allOf": [{ "$ref": "#/$defs/base" }, { "required": ["spec", "schema"] }]
40+
},
41+
42+
"business_schema": {
43+
"title": "Capability (Business Schema)",
44+
"description": "Capability configuration for business/merchant level. May include business-specific config overrides.",
45+
"allOf": [{ "$ref": "#/$defs/base" }]
46+
},
47+
48+
"response_schema": {
49+
"title": "Capability (Response Schema)",
50+
"description": "Capability reference in responses. Only name/version required to confirm active capabilities.",
51+
"allOf": [{ "$ref": "#/$defs/base" }]
52+
}
53+
}
54+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://ucp.dev/schemas/payment_handler.json",
4+
"title": "Payment Handler",
5+
"description": "Schema for UCP payment handlers. Handlers define how payment instruments are processed.",
6+
7+
"$defs": {
8+
"base": {
9+
"allOf": [
10+
{ "$ref": "ucp.json#/$defs/entity" },
11+
{
12+
"type": "object",
13+
"required": ["id"]
14+
},
15+
{
16+
"type": "object",
17+
"properties": {
18+
"available_instruments": {
19+
"type": "array",
20+
"items": { "$ref": "shopping/types/available_payment_instrument.json" },
21+
"description": "Instrument types this handler supports, with optional constraints. When absent, every instrument should be considered available.",
22+
"minItems": 1
23+
}
24+
}
25+
}
26+
]
27+
},
28+
29+
"platform_schema": {
30+
"title": "Payment Handler (Platform Schema)",
31+
"description": "Platform declaration for discovery profiles. May include partial config state required for discovery.",
32+
"allOf": [{ "$ref": "#/$defs/base" }, { "required": ["spec", "schema"] }]
33+
},
34+
35+
"business_schema": {
36+
"title": "Payment Handler (Business Schema)",
37+
"description": "Business declaration for discovery profiles. May include partial config state required for discovery.",
38+
"allOf": [{ "$ref": "#/$defs/base" }]
39+
},
40+
41+
"response_schema": {
42+
"title": "Payment Handler (Response Schema)",
43+
"description": "Handler reference in responses. May include full config state for runtime usage of the handler.",
44+
"allOf": [{ "$ref": "#/$defs/base" }]
45+
}
46+
}
47+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://ucp.dev/schemas/service.json",
4+
"title": "UCP Service",
5+
"description": "Service binding for a specific transport. Each transport binding is a separate entry in the service array.",
6+
7+
"$defs": {
8+
"base": {
9+
"allOf": [
10+
{ "$ref": "ucp.json#/$defs/entity" },
11+
{
12+
"type": "object",
13+
"required": ["transport"],
14+
"properties": {
15+
"transport": {
16+
"type": "string",
17+
"enum": ["rest", "mcp", "a2a", "embedded"],
18+
"description": "Transport protocol for this service binding."
19+
},
20+
"endpoint": {
21+
"type": "string",
22+
"format": "uri",
23+
"description": "Endpoint URL for this transport binding."
24+
}
25+
}
26+
}
27+
]
28+
},
29+
30+
"platform_schema": {
31+
"title": "Service (Platform Schema)",
32+
"description": "Full service declaration for platform-level discovery. All transports require `version`, `spec`, and `transport`. REST, MCP, and embedded additionally require `schema`.",
33+
"allOf": [
34+
{ "$ref": "#/$defs/base" },
35+
{ "required": ["spec"] },
36+
{
37+
"anyOf": [
38+
{
39+
"properties": { "transport": { "const": "rest" } },
40+
"required": ["schema"]
41+
},
42+
{
43+
"properties": { "transport": { "const": "mcp" } },
44+
"required": ["schema"]
45+
},
46+
{
47+
"properties": { "transport": { "const": "a2a" } }
48+
},
49+
{
50+
"properties": { "transport": { "const": "embedded" } },
51+
"required": ["schema"]
52+
}
53+
]
54+
}
55+
]
56+
},
57+
58+
"business_schema": {
59+
"title": "Service (Business Schema)",
60+
"description": "Service binding for business/merchant configuration. May override platform endpoints.",
61+
"allOf": [
62+
{ "$ref": "#/$defs/base" },
63+
{
64+
"anyOf": [
65+
{
66+
"properties": { "transport": { "const": "rest" } },
67+
"required": ["endpoint"]
68+
},
69+
{
70+
"properties": { "transport": { "const": "mcp" } },
71+
"required": ["endpoint"]
72+
},
73+
{
74+
"properties": { "transport": { "const": "a2a" } },
75+
"required": ["endpoint"]
76+
},
77+
{
78+
"properties": {
79+
"transport": { "const": "embedded" },
80+
"config": { "$ref": "transports/embedded_config.json" }
81+
}
82+
}
83+
]
84+
}
85+
]
86+
},
87+
88+
"response_schema": {
89+
"title": "Service (Response Schema)",
90+
"description": "Service binding in API responses. Includes per-resource transport configuration via typed config.",
91+
"allOf": [
92+
{ "$ref": "#/$defs/base" },
93+
{
94+
"anyOf": [
95+
{
96+
"properties": { "transport": { "const": "rest" } }
97+
},
98+
{
99+
"properties": { "transport": { "const": "mcp" } }
100+
},
101+
{
102+
"properties": { "transport": { "const": "a2a" } }
103+
},
104+
{
105+
"properties": {
106+
"transport": { "const": "embedded" },
107+
"config": { "$ref": "transports/embedded_config.json" }
108+
}
109+
}
110+
]
111+
}
112+
]
113+
}
114+
}
115+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://ucp.dev/schemas/shopping/types/available_payment_instrument.json",
4+
"title": "Available Payment Instrument",
5+
"description": "An instrument type available from a payment handler with optional constraints.",
6+
"type": "object",
7+
"required": ["type"],
8+
"properties": {
9+
"type": {
10+
"type": "string",
11+
"description": "The instrument type identifier (e.g., 'card', 'gift_card'). References an instrument schema's type constant."
12+
},
13+
"constraints": {
14+
"type": "object",
15+
"additionalProperties": true,
16+
"description": "Constraints on this instrument type. Structure depends on instrument type and active capabilities.",
17+
"minProperties": 1
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)