diff --git a/Cargo.toml b/Cargo.toml index 2746300..bca990f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" readme = "README.md" keywords = ["email", "schema", "json-schema", "agents"] categories = ["data-structures", "email"] -include = ["src/**", "schemas/**", "Cargo.toml", "README.md", "LICENSE"] +include = ["src/**", "schemas/**", "contracts/**", "Cargo.toml", "README.md", "LICENSE"] [dev-dependencies] serde_json = "1" diff --git a/README.md b/README.md index b36d2c0..1658447 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,13 @@ Rust 1.70 or newer is required. ## Use a schema ```rust -use mailschema::{Schema, MAP_0_1_SCHEMA}; +use mailschema::{Schema, CONTENT_REVIEW_0_1_CONTRACT, MAP_0_1_SCHEMA}; assert_eq!(Schema::Map01.as_str(), MAP_0_1_SCHEMA); let content_review = Schema::ContentReview01.as_str(); assert!(content_review.contains("Content Review")); +assert!(CONTENT_REVIEW_0_1_CONTRACT.contains("MapTypeContract")); ``` The crate exposes four Draft 2020-12 documents: @@ -36,6 +37,8 @@ The crate exposes four Draft 2020-12 documents: Parse the strings with your JSON library and pass them to a Draft 2020-12 validator with format checking enabled. The crate deliberately does not select a validation engine for its consumers. +`CONTENT_REVIEW_0_1_CONTRACT` exposes the canonical type contract used by MAP wire messages. + ## Trust boundary A valid document is structured input. Schema validation does not authenticate a service, grant authority, approve an action, check Registry references or establish product conformance. Implementations must apply their own endpoint trust, credentials, permissions and policy before executing a request. diff --git a/UPSTREAM.md b/UPSTREAM.md index d358fa9..4d7b155 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -4,4 +4,4 @@ Version `0.1.2` was prepared from [`mailschema/mailschema@eddc745`](https://gith The included contribution schema has SHA-256 digest `a8a241b4837971a6251bcc135c704283536f4fb825fbbaedaf1598c6e688d564`. The canonical schemas live in the main MailSchema repository; this repository owns the Rust package surface and its release history. -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. +The unreleased MAP contract projection is derived from [`mailschema/mailschema@e2f2cc6`](https://github.com/mailschema/mailschema/commit/e2f2cc6983173e5aba1da27f1f8bff37336efd77). 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. diff --git a/contracts/content-review-0.1.json b/contracts/content-review-0.1.json new file mode 100644 index 0000000..922baef --- /dev/null +++ b/contracts/content-review-0.1.json @@ -0,0 +1,58 @@ +{ + "kind": "MapTypeContract", + "id": "https://mailschema.org/types/content-review", + "version": "0.1", + "profile": "https://mailschema.org/profiles/map/0.1", + "target": "Service-owned content at the exact revision and digest named in the request.", + "requestSchema": { + "url": "https://mailschema.org/schemas/content-review-0.1.schema.json", + "canonicalDigest": "sha-256:cdad1149ab193d1bf71cb9463e3c4b7331ed56b0123e225adf68f70a2022b29d" + }, + "operations": [ + { + "id": "request-changes", + "effect": "Record review feedback on the named revision.", + "results": [ + { + "state": "accepted", + "outputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "feedbackRecorded": { "const": true } + }, + "required": ["feedbackRecorded"] + } + } + ] + }, + { + "id": "approve", + "effect": "Record approval of the named revision.", + "results": [ + { + "state": "approval-required", + "outputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "approvalUrl": { "type": "string", "format": "uri", "pattern": "^https://" } + }, + "required": ["approvalUrl"] + } + }, + { + "state": "completed", + "outputSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "decision": { "const": "approved" } + }, + "required": ["decision"] + } + } + ] + } + ] +} diff --git a/schemas/content-review-0.1.schema.json b/schemas/content-review-0.1.schema.json index 61c1f22..57fc90f 100644 --- a/schemas/content-review-0.1.schema.json +++ b/schemas/content-review-0.1.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://mailschema.org/schemas/content-review-0.1.schema.json", - "title": "Content Review 0.1 MAP request", + "title": "Content Review 0.1 MAP request binding", "allOf": [ { "$ref": "https://mailschema.org/schemas/map-0.1.schema.json#/$defs/request" }, { @@ -11,10 +11,7 @@ "type": "object", "properties": { "id": { "const": "https://mailschema.org/types/content-review" }, - "version": { "const": "0.1" }, - "recordDigest": { - "const": "sha-256:0e6365df1bf904f2475f972ec66a5561edc0bfaae69fd7a1b41f53b40bf8ac1c" - } + "version": { "const": "0.1" } } } } diff --git a/schemas/map-0.1.schema.json b/schemas/map-0.1.schema.json index 2cc1dfe..352cd5d 100644 --- a/schemas/map-0.1.schema.json +++ b/schemas/map-0.1.schema.json @@ -32,7 +32,7 @@ "type": "string", "pattern": "^sha-256:[a-f0-9]{64}$" }, - "recordDigest": { + "contractDigest": { "type": "string", "pattern": "^sha-256:[a-f0-9]{64}$" }, @@ -46,9 +46,9 @@ "pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$", "maxLength": 80 }, - "recordDigest": { "$ref": "#/$defs/recordDigest" } + "contractDigest": { "$ref": "#/$defs/contractDigest" } }, - "required": ["id", "version", "recordDigest"] + "required": ["id", "version", "contractDigest"] }, "target": { "type": "object", @@ -172,7 +172,6 @@ "profile": { "$ref": "#/$defs/profile" }, "requestId": { "$ref": "#/$defs/uuidUrn" }, "interactionId": { "$ref": "#/$defs/uuidUrn" }, - "requestedAt": { "type": "string", "format": "date-time" }, "type": { "$ref": "#/$defs/typeReference" }, "operation": { "type": "string", @@ -187,7 +186,6 @@ "profile", "requestId", "interactionId", - "requestedAt", "type", "operation", "target", @@ -273,61 +271,139 @@ }, "target": { "$ref": "#/$defs/target" } }, - "required": [ - "type", - "title", - "status", - "detail", - "instance", - "profile", - "requestId", - "interactionId", - "code" - ], + "required": ["type", "title", "status", "detail"], + "dependentRequired": { + "instance": ["profile", "requestId", "code"], + "profile": ["instance", "requestId", "code"], + "requestId": ["instance", "profile", "code"], + "interactionId": ["instance", "profile", "requestId", "code"], + "code": ["instance", "profile", "requestId"] + }, "allOf": [ + { + "if": { + "properties": { "code": { "not": { "const": "result-not-found" } } }, + "required": ["code"] + }, + "then": { "required": ["interactionId"] } + }, { "if": { "properties": { "code": { "const": "invalid-request" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/invalid-request" }, "status": { "const": 400 } } } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/invalid-request" }, + "status": { "const": 400 } + } + } }, { - "if": { "properties": { "code": { "const": "authentication-required" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/authentication-required" }, "status": { "const": 401 } } } + "if": { + "properties": { "code": { "const": "authentication-required" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/authentication-required" }, + "status": { "const": 401 } + } + } }, { "if": { "properties": { "code": { "const": "refused" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/refused" }, "status": { "const": 403 } } } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/refused" }, + "status": { "const": 403 } + } + } }, { "if": { "properties": { "code": { "const": "result-not-found" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/result-not-found" }, "status": { "const": 404 } } } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/result-not-found" }, + "status": { "const": 404 } + } + } }, { "if": { "properties": { "code": { "const": "stale-target" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/stale-target" }, "status": { "const": 409 } }, "required": ["target"] } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/stale-target" }, + "status": { "const": 409 } + }, + "required": ["target"] + } }, { - "if": { "properties": { "code": { "const": "idempotency-conflict" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/idempotency-conflict" }, "status": { "const": 409 } } } + "if": { + "properties": { "code": { "const": "idempotency-conflict" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/idempotency-conflict" }, + "status": { "const": 409 } + } + } }, { - "if": { "properties": { "code": { "const": "request-in-progress" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/request-in-progress" }, "status": { "const": 409 } } } + "if": { + "properties": { "code": { "const": "request-in-progress" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/request-in-progress" }, + "status": { "const": 409 } + } + } }, { - "if": { "properties": { "code": { "const": "expired-interaction" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/expired-interaction" }, "status": { "const": 410 } } } + "if": { + "properties": { "code": { "const": "expired-interaction" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/expired-interaction" }, + "status": { "const": 410 } + } + } }, { - "if": { "properties": { "code": { "const": "unsupported-profile" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/unsupported-profile" }, "status": { "const": 422 } } } + "if": { + "properties": { "code": { "const": "unsupported-profile" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/unsupported-profile" }, + "status": { "const": 422 } + } + } }, { "if": { "properties": { "code": { "const": "unsupported-type" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/unsupported-type" }, "status": { "const": 422 } } } + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/unsupported-type" }, + "status": { "const": 422 } + } + } }, { - "if": { "properties": { "code": { "const": "unsupported-operation" } }, "required": ["code"] }, - "then": { "properties": { "type": { "const": "https://mailschema.org/problems/unsupported-operation" }, "status": { "const": 422 } } } + "if": { + "properties": { "code": { "const": "unsupported-operation" } }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { "const": "https://mailschema.org/problems/unsupported-operation" }, + "status": { "const": 422 } + } + } } ] } diff --git a/src/lib.rs b/src/lib.rs index cabd798..d16a0b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,11 @@ pub const RECORD_SCHEMA: &str = include_str!("../schemas/record.schema.json"); pub const MAP_0_1_SCHEMA: &str = include_str!("../schemas/map-0.1.schema.json"); /// Content Review 0.1 request binding. -pub const CONTENT_REVIEW_0_1_SCHEMA: &str = include_str!("../schemas/content-review-0.1.schema.json"); +pub const CONTENT_REVIEW_0_1_SCHEMA: &str = + include_str!("../schemas/content-review-0.1.schema.json"); + +/// Canonical Content Review 0.1 type contract. +pub const CONTENT_REVIEW_0_1_CONTRACT: &str = include_str!("../contracts/content-review-0.1.json"); /// Supported local schema documents. #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -51,13 +55,25 @@ mod tests { Schema::ContentReview01, ] { let value: serde_json::Value = serde_json::from_str(schema.as_str()).unwrap(); - assert_eq!(value["$schema"], "https://json-schema.org/draft/2020-12/schema"); + assert_eq!( + value["$schema"], + "https://json-schema.org/draft/2020-12/schema" + ); } let contribution: serde_json::Value = serde_json::from_str(CONTRIBUTION_SCHEMA).unwrap(); assert!(contribution["$defs"]["record"].is_object()); let map: serde_json::Value = serde_json::from_str(MAP_0_1_SCHEMA).unwrap(); - assert_eq!(map["$id"], "https://mailschema.org/schemas/map-0.1.schema.json"); + assert_eq!( + map["$id"], + "https://mailschema.org/schemas/map-0.1.schema.json" + ); let record: serde_json::Value = serde_json::from_str(RECORD_SCHEMA).unwrap(); assert_eq!(record["$ref"], "#/$defs/record"); + let contract: serde_json::Value = + serde_json::from_str(CONTENT_REVIEW_0_1_CONTRACT).unwrap(); + assert_eq!( + contract["id"], + "https://mailschema.org/types/content-review" + ); } }