Skip to content

Commit fda1cac

Browse files
committed
fix(map): expose the canonical type contract
1 parent 92724b1 commit fda1cac

11 files changed

Lines changed: 188 additions & 45 deletions

‎README.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ Python 3.10 or newer is required.
1818
## Validate MAP documents
1919

2020
```python
21-
from mailschema import validate_map_document, validate_content_review_request
21+
from mailschema import get_content_review_contract, validate_map_document, validate_content_review_request
2222

2323
validate_map_document(description)
2424
validate_content_review_request(request)
25+
contract = get_content_review_contract()
2526
```
2627

2728
The CLI performs the same checks against local JSON files:
@@ -43,7 +44,7 @@ schema = get_contribution_schema()
4344

4445
`validate_record`, `record_errors` and `get_record_schema` handle expanded Registry records. Pass `--record` to the CLI for the corresponding record format.
4546

46-
The package bundles the MAP 0.1, Content Review 0.1 and Registry schemas. It uses the established `jsonschema` and `referencing` libraries for validation and performs no network requests.
47+
The package bundles the MAP 0.1 and Registry schemas, the Content Review 0.1 request schema and its canonical type contract. It uses the established `jsonschema` and `referencing` libraries for validation and performs no network requests.
4748

4849
## Trust boundary
4950

‎UPSTREAM.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ Version `0.1.2` was prepared from [`mailschema/mailschema@eddc745`](https://gith
44

55
The included contribution schema has SHA-256 digest `a8a241b4837971a6251bcc135c704283536f4fb825fbbaedaf1598c6e688d564`. The canonical schemas live in the main MailSchema repository; this repository owns the Python package surface and its release history.
66

7-
The unreleased MAP contract projection is derived from [`mailschema/mailschema@ab91b37`](https://github.com/mailschema/mailschema/commit/ab91b37d29dbf95a2e208a34a144bf5f7c26d0a6). Its MAP schema digest is `d908a960f53fade9ab47876bf69b1b3d037adeb7096091e22ca71fbc3909429c`; its Content Review schema digest is `dba2a2f1e57f843f43080f5a40ced2d9c84e7c7036ec306c371a82d39f6d6c9f`. These files are projections of that source commit and do not independently define MAP.
7+
The unreleased MAP contract projection is derived from [`mailschema/mailschema@5ba4647`](https://github.com/mailschema/mailschema/commit/5ba4647836e1a827e5e8ef51894abaee30a668bd). Its MAP schema digest is `c39befeecf8643e8beda1f53a8d1411482b68ddf14974f3806cfa95776103f85`; its Content Review schema digest is `10e2263c820ba3a6ef0381c377078e2c1f324c3b142a6f8caf92b298cc66c692`; and its Content Review contract byte digest is `754ab7245ddb09ff4da000fccc60d746724379d4146ea227973e5d87f105f800`. These files are exact projections of that source commit and do not independently define MAP.

‎src/mailschema/__init__.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ def get_content_review_schema() -> dict[str, Any]:
2929
return _bundled("content-review-0.1.schema.json")
3030

3131

32+
def get_content_review_contract() -> dict[str, Any]:
33+
"""Return a fresh copy of the canonical Content Review 0.1 type contract."""
34+
return _bundled("content-review-0.1.contract.json")
35+
36+
3237
def get_record_schema() -> dict[str, Any]:
3338
"""Return the schema for expanded Registry records."""
3439
schema = get_contribution_schema()
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"kind": "MapTypeContract",
3+
"id": "https://mailschema.org/types/content-review",
4+
"version": "0.1",
5+
"profile": "https://mailschema.org/profiles/map/0.1",
6+
"target": "Service-owned content at the exact revision and digest named in the request.",
7+
"requestSchema": {
8+
"url": "https://mailschema.org/schemas/content-review-0.1.schema.json",
9+
"canonicalDigest": "sha-256:cdad1149ab193d1bf71cb9463e3c4b7331ed56b0123e225adf68f70a2022b29d"
10+
},
11+
"operations": [
12+
{
13+
"id": "request-changes",
14+
"effect": "Record review feedback on the named revision.",
15+
"results": [
16+
{
17+
"state": "accepted",
18+
"outputSchema": {
19+
"type": "object",
20+
"additionalProperties": false,
21+
"properties": {
22+
"feedbackRecorded": { "const": true }
23+
},
24+
"required": ["feedbackRecorded"]
25+
}
26+
}
27+
]
28+
},
29+
{
30+
"id": "approve",
31+
"effect": "Record approval of the named revision.",
32+
"results": [
33+
{
34+
"state": "approval-required",
35+
"outputSchema": {
36+
"type": "object",
37+
"additionalProperties": false,
38+
"properties": {
39+
"approvalUrl": { "type": "string", "format": "uri", "pattern": "^https://" }
40+
},
41+
"required": ["approvalUrl"]
42+
}
43+
},
44+
{
45+
"state": "completed",
46+
"outputSchema": {
47+
"type": "object",
48+
"additionalProperties": false,
49+
"properties": {
50+
"decision": { "const": "approved" }
51+
},
52+
"required": ["decision"]
53+
}
54+
}
55+
]
56+
}
57+
]
58+
}

‎src/mailschema/content-review-0.1.schema.json‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://json-schema.org/draft/2020-12/schema",
33
"$id": "https://mailschema.org/schemas/content-review-0.1.schema.json",
4-
"title": "Content Review 0.1 MAP request",
4+
"title": "Content Review 0.1 MAP request binding",
55
"allOf": [
66
{ "$ref": "https://mailschema.org/schemas/map-0.1.schema.json#/$defs/request" },
77
{
@@ -11,10 +11,7 @@
1111
"type": "object",
1212
"properties": {
1313
"id": { "const": "https://mailschema.org/types/content-review" },
14-
"version": { "const": "0.1" },
15-
"recordDigest": {
16-
"const": "sha-256:0e6365df1bf904f2475f972ec66a5561edc0bfaae69fd7a1b41f53b40bf8ac1c"
17-
}
14+
"version": { "const": "0.1" }
1815
}
1916
}
2017
}

‎src/mailschema/map-0.1.schema.json‎

Lines changed: 109 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"type": "string",
3333
"pattern": "^sha-256:[a-f0-9]{64}$"
3434
},
35-
"recordDigest": {
35+
"contractDigest": {
3636
"type": "string",
3737
"pattern": "^sha-256:[a-f0-9]{64}$"
3838
},
@@ -46,9 +46,9 @@
4646
"pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$",
4747
"maxLength": 80
4848
},
49-
"recordDigest": { "$ref": "#/$defs/recordDigest" }
49+
"contractDigest": { "$ref": "#/$defs/contractDigest" }
5050
},
51-
"required": ["id", "version", "recordDigest"]
51+
"required": ["id", "version", "contractDigest"]
5252
},
5353
"target": {
5454
"type": "object",
@@ -172,7 +172,6 @@
172172
"profile": { "$ref": "#/$defs/profile" },
173173
"requestId": { "$ref": "#/$defs/uuidUrn" },
174174
"interactionId": { "$ref": "#/$defs/uuidUrn" },
175-
"requestedAt": { "type": "string", "format": "date-time" },
176175
"type": { "$ref": "#/$defs/typeReference" },
177176
"operation": {
178177
"type": "string",
@@ -187,7 +186,6 @@
187186
"profile",
188187
"requestId",
189188
"interactionId",
190-
"requestedAt",
191189
"type",
192190
"operation",
193191
"target",
@@ -273,61 +271,139 @@
273271
},
274272
"target": { "$ref": "#/$defs/target" }
275273
},
276-
"required": [
277-
"type",
278-
"title",
279-
"status",
280-
"detail",
281-
"instance",
282-
"profile",
283-
"requestId",
284-
"interactionId",
285-
"code"
286-
],
274+
"required": ["type", "title", "status", "detail"],
275+
"dependentRequired": {
276+
"instance": ["profile", "requestId", "code"],
277+
"profile": ["instance", "requestId", "code"],
278+
"requestId": ["instance", "profile", "code"],
279+
"interactionId": ["instance", "profile", "requestId", "code"],
280+
"code": ["instance", "profile", "requestId"]
281+
},
287282
"allOf": [
283+
{
284+
"if": {
285+
"properties": { "code": { "not": { "const": "result-not-found" } } },
286+
"required": ["code"]
287+
},
288+
"then": { "required": ["interactionId"] }
289+
},
288290
{
289291
"if": { "properties": { "code": { "const": "invalid-request" } }, "required": ["code"] },
290-
"then": { "properties": { "type": { "const": "https://mailschema.org/problems/invalid-request" }, "status": { "const": 400 } } }
292+
"then": {
293+
"properties": {
294+
"type": { "const": "https://mailschema.org/problems/invalid-request" },
295+
"status": { "const": 400 }
296+
}
297+
}
291298
},
292299
{
293-
"if": { "properties": { "code": { "const": "authentication-required" } }, "required": ["code"] },
294-
"then": { "properties": { "type": { "const": "https://mailschema.org/problems/authentication-required" }, "status": { "const": 401 } } }
300+
"if": {
301+
"properties": { "code": { "const": "authentication-required" } },
302+
"required": ["code"]
303+
},
304+
"then": {
305+
"properties": {
306+
"type": { "const": "https://mailschema.org/problems/authentication-required" },
307+
"status": { "const": 401 }
308+
}
309+
}
295310
},
296311
{
297312
"if": { "properties": { "code": { "const": "refused" } }, "required": ["code"] },
298-
"then": { "properties": { "type": { "const": "https://mailschema.org/problems/refused" }, "status": { "const": 403 } } }
313+
"then": {
314+
"properties": {
315+
"type": { "const": "https://mailschema.org/problems/refused" },
316+
"status": { "const": 403 }
317+
}
318+
}
299319
},
300320
{
301321
"if": { "properties": { "code": { "const": "result-not-found" } }, "required": ["code"] },
302-
"then": { "properties": { "type": { "const": "https://mailschema.org/problems/result-not-found" }, "status": { "const": 404 } } }
322+
"then": {
323+
"properties": {
324+
"type": { "const": "https://mailschema.org/problems/result-not-found" },
325+
"status": { "const": 404 }
326+
}
327+
}
303328
},
304329
{
305330
"if": { "properties": { "code": { "const": "stale-target" } }, "required": ["code"] },
306-
"then": { "properties": { "type": { "const": "https://mailschema.org/problems/stale-target" }, "status": { "const": 409 } }, "required": ["target"] }
331+
"then": {
332+
"properties": {
333+
"type": { "const": "https://mailschema.org/problems/stale-target" },
334+
"status": { "const": 409 }
335+
},
336+
"required": ["target"]
337+
}
307338
},
308339
{
309-
"if": { "properties": { "code": { "const": "idempotency-conflict" } }, "required": ["code"] },
310-
"then": { "properties": { "type": { "const": "https://mailschema.org/problems/idempotency-conflict" }, "status": { "const": 409 } } }
340+
"if": {
341+
"properties": { "code": { "const": "idempotency-conflict" } },
342+
"required": ["code"]
343+
},
344+
"then": {
345+
"properties": {
346+
"type": { "const": "https://mailschema.org/problems/idempotency-conflict" },
347+
"status": { "const": 409 }
348+
}
349+
}
311350
},
312351
{
313-
"if": { "properties": { "code": { "const": "request-in-progress" } }, "required": ["code"] },
314-
"then": { "properties": { "type": { "const": "https://mailschema.org/problems/request-in-progress" }, "status": { "const": 409 } } }
352+
"if": {
353+
"properties": { "code": { "const": "request-in-progress" } },
354+
"required": ["code"]
355+
},
356+
"then": {
357+
"properties": {
358+
"type": { "const": "https://mailschema.org/problems/request-in-progress" },
359+
"status": { "const": 409 }
360+
}
361+
}
315362
},
316363
{
317-
"if": { "properties": { "code": { "const": "expired-interaction" } }, "required": ["code"] },
318-
"then": { "properties": { "type": { "const": "https://mailschema.org/problems/expired-interaction" }, "status": { "const": 410 } } }
364+
"if": {
365+
"properties": { "code": { "const": "expired-interaction" } },
366+
"required": ["code"]
367+
},
368+
"then": {
369+
"properties": {
370+
"type": { "const": "https://mailschema.org/problems/expired-interaction" },
371+
"status": { "const": 410 }
372+
}
373+
}
319374
},
320375
{
321-
"if": { "properties": { "code": { "const": "unsupported-profile" } }, "required": ["code"] },
322-
"then": { "properties": { "type": { "const": "https://mailschema.org/problems/unsupported-profile" }, "status": { "const": 422 } } }
376+
"if": {
377+
"properties": { "code": { "const": "unsupported-profile" } },
378+
"required": ["code"]
379+
},
380+
"then": {
381+
"properties": {
382+
"type": { "const": "https://mailschema.org/problems/unsupported-profile" },
383+
"status": { "const": 422 }
384+
}
385+
}
323386
},
324387
{
325388
"if": { "properties": { "code": { "const": "unsupported-type" } }, "required": ["code"] },
326-
"then": { "properties": { "type": { "const": "https://mailschema.org/problems/unsupported-type" }, "status": { "const": 422 } } }
389+
"then": {
390+
"properties": {
391+
"type": { "const": "https://mailschema.org/problems/unsupported-type" },
392+
"status": { "const": 422 }
393+
}
394+
}
327395
},
328396
{
329-
"if": { "properties": { "code": { "const": "unsupported-operation" } }, "required": ["code"] },
330-
"then": { "properties": { "type": { "const": "https://mailschema.org/problems/unsupported-operation" }, "status": { "const": 422 } } }
397+
"if": {
398+
"properties": { "code": { "const": "unsupported-operation" } },
399+
"required": ["code"]
400+
},
401+
"then": {
402+
"properties": {
403+
"type": { "const": "https://mailschema.org/problems/unsupported-operation" },
404+
"status": { "const": 422 }
405+
}
406+
}
331407
}
332408
]
333409
}

‎tests/content-review.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
"exception": "An approval request for revision 3 is refused once that revision is stale. Approval of revision 4 does not send the campaign."
3737
},
3838
"references": [
39+
{
40+
"title": "Content Review 0.1 contract",
41+
"href": "https://mailschema.org/contracts/content-review-0.1.json",
42+
"description": "Normative canonical type contract used by MAP wire messages."
43+
},
3944
{
4045
"title": "Schema.org Actions",
4146
"href": "https://schema.org/docs/actions.html",

‎tests/map-description.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"type": {
77
"id": "https://mailschema.org/types/content-review",
88
"version": "0.1",
9-
"recordDigest": "sha-256:0e6365df1bf904f2475f972ec66a5561edc0bfaae69fd7a1b41f53b40bf8ac1c"
9+
"contractDigest": "sha-256:deba8fc588c83c7cdf9d4e47d23b114861aea35c3daaf71854dc6c626c8a1d05"
1010
},
1111
"describedAt": "2026-09-23T01:00:00Z",
1212
"expiresAt": "2026-09-30T01:00:00Z",

‎tests/map-request.json‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
"profile": "https://mailschema.org/profiles/map/0.1",
44
"requestId": "urn:uuid:018f47a2-b4d3-7c02-b491-7bdf2eaac67c",
55
"interactionId": "urn:uuid:018f47a2-5d7c-7b11-9a3d-4d2160b85b10",
6-
"requestedAt": "2026-09-23T01:06:00Z",
76
"type": {
87
"id": "https://mailschema.org/types/content-review",
98
"version": "0.1",
10-
"recordDigest": "sha-256:0e6365df1bf904f2475f972ec66a5561edc0bfaae69fd7a1b41f53b40bf8ac1c"
9+
"contractDigest": "sha-256:deba8fc588c83c7cdf9d4e47d23b114861aea35c3daaf71854dc6c626c8a1d05"
1110
},
1211
"operation": "approve",
1312
"target": {

‎tests/map-result.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"type": {
77
"id": "https://mailschema.org/types/content-review",
88
"version": "0.1",
9-
"recordDigest": "sha-256:0e6365df1bf904f2475f972ec66a5561edc0bfaae69fd7a1b41f53b40bf8ac1c"
9+
"contractDigest": "sha-256:deba8fc588c83c7cdf9d4e47d23b114861aea35c3daaf71854dc6c626c8a1d05"
1010
},
1111
"operation": "approve",
1212
"state": "completed",

0 commit comments

Comments
 (0)