mailschema 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -7
  3. data/README.md +29 -88
  4. data/artifacts/context.jsonld +50 -0
  5. data/artifacts/contract.schema.json +206 -0
  6. data/artifacts/core.schema.json +210 -0
  7. data/artifacts/implementation.schema.json +173 -0
  8. data/artifacts/profile.json +27 -0
  9. data/lib/mailschema/artifacts.rb +32 -75
  10. data/lib/mailschema/authentication.rb +77 -0
  11. data/lib/mailschema/contract.rb +184 -281
  12. data/lib/mailschema/description.rb +56 -0
  13. data/lib/mailschema/formats.rb +77 -0
  14. data/lib/mailschema/header.rb +75 -0
  15. data/lib/mailschema/implementation.rb +27 -0
  16. data/lib/mailschema/jcs.rb +11 -10
  17. data/lib/mailschema/json.rb +190 -0
  18. data/lib/mailschema/message.rb +127 -12
  19. data/lib/mailschema/origin.rb +18 -0
  20. data/lib/mailschema/patterns.rb +173 -0
  21. data/lib/mailschema/subschemas.rb +70 -0
  22. data/lib/mailschema/version.rb +1 -1
  23. data/lib/mailschema.rb +16 -26
  24. data/sig/mailschema.rbs +76 -68
  25. metadata +29 -54
  26. data/contexts/map-0.2.jsonld +0 -91
  27. data/lib/mailschema/capability.rb +0 -41
  28. data/lib/mailschema/document.rb +0 -79
  29. data/lib/mailschema/documents.rb +0 -127
  30. data/lib/mailschema/forms.rb +0 -41
  31. data/lib/mailschema/http.rb +0 -25
  32. data/lib/mailschema/limits.rb +0 -56
  33. data/lib/mailschema/pointer.rb +0 -46
  34. data/lib/mailschema/references.rb +0 -62
  35. data/lib/mailschema/schema_walk.rb +0 -29
  36. data/schemas/contribution.schema.json +0 -874
  37. data/schemas/forms-0.1.schema.json +0 -329
  38. data/schemas/map-0.2.schema.json +0 -1033
  39. data/schemas/type-contract-0.2.schema.json +0 -179
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd3f1015f63294ef11a1d3a9ff43bcc18af342fd1a55e13e3d04d94b2091302e
4
- data.tar.gz: 1f697fbcc3877ad1ea40d70c2ca33f2adf4aa6412ebcef98ea0d26968708cead
3
+ metadata.gz: d40b5366532ded5404a82499db609a8e30e4edd70df68befef93c6c227cfb461
4
+ data.tar.gz: bd03b7643aa9589044fcf10fa2bf63b35b3856f370198af3663c2beb6343280d
5
5
  SHA512:
6
- metadata.gz: e5a64d0eb6e599247ecd12397b3f370e55ac628d425dfc97f0a3b0450ecc1abf8496601b57ec25eae53d45d65c68ef550459d22972fe3db9065c902bdf0f0742
7
- data.tar.gz: 0dd2ea492f2280c0dc443e46b655ad244e363243613599e61eab58b06f6145d03e794d0db9d59871f91c1686a15d42695a4c27394426f6c4357486c498997647
6
+ metadata.gz: 8195ac0b45dfc6ee758e0c25b8e46195f4768b07ebde12debf8629377c10e97536687dcb2bf0848c94d34921803534dbb1611b3a3ace46eb6f0f850d280c0be2
7
+ data.tar.gz: 42b7343562247873c5c01b96a58477e0775d19274a7ce94c740c71e190bb639a5f476809446d48043f44599d8e80f855f918cf6447e229c450c6787459d081ff
data/CHANGELOG.md CHANGED
@@ -1,10 +1,7 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [0.3.0] - 2026-10-07
4
4
 
5
- ## [0.2.0] - 2026-09-26
6
-
7
- - First release, for Mail Action Protocol 0.2.
8
- - Parse MAP documents as I-JSON within the core limits, and canonicalize and digest them with RFC 8785.
9
- - Verify a vendored type contract against its pinned digest, and check descriptions, requests, inputs and results against it.
10
- - Build results, state transitions and problems, and apply result retention and approval expiry.
5
+ - Mail Action Protocol 0.3: strict MAP JSON parsing, RFC 8785 canonicalization and digests, descriptions, type contracts and implementation records, with the reference implementation's rules and messages.
6
+ - Extract the one MAP part of a received message, and build the part that carries a description.
7
+ - Decide whether a verified DKIM signature qualifies a message for MAP actions.
data/README.md CHANGED
@@ -1,114 +1,55 @@
1
1
  # mailschema for Ruby
2
2
 
3
- Mail Action Protocol 0.2 tooling for Ruby. The gem parses, canonicalizes, digests and validates MAP documents, verifies the type contracts an implementation vendors, and builds results and problems.
3
+ [Mail Action Protocol](https://mailschema.org) (MAP) 0.3 processing in Ruby. The gem reads MAP JSON strictly from its original bytes, computes RFC 8785 digests, and checks descriptions, type contracts and implementation records with the same rules and messages as the reference implementation. It also finds the one MAP part of a received email, builds that part for a message you send, and decides whether a verified DKIM signature qualifies a message for MAP actions.
4
4
 
5
- It does not establish endpoint trust, verify email authentication, grant authority or send email. Possession mode needs DKIM and DMARC checks on the raw message, which this gem leaves to the client.
5
+ It does not verify DKIM, query DNS, fetch anything an email names, grant authority or send email. Those belong to your verifier, your trusted service binding and your host policy.
6
6
 
7
7
  ```sh
8
- gem install mailschema
8
+ gem install mailschema -v 0.3.0
9
9
  ```
10
10
 
11
- Ruby 3.3 or later.
11
+ Ruby 3.3 or later. The gem depends on `json_schemer` and `mail`.
12
12
 
13
- ## A service
13
+ ## Example
14
14
 
15
- Vendor the exact contract and request schema your service implements, and pin the contract by the digest you reviewed. The gem refuses any other contract.
15
+ Parse a description, load the exact contract you resolved from your catalogue, and check the description against it:
16
16
 
17
17
  ```ruby
18
18
  require "mailschema"
19
19
 
20
- CONTRACT = Mailschema::Contract.new(
21
- JSON.parse(File.read("config/mailschema/content-review-0.3.json")),
22
- JSON.parse(File.read("config/mailschema/content-review-0.3.schema.json")),
23
- digest: "sha-256:…"
24
- )
20
+ description = Mailschema.parse_description(json)
21
+ contract = Mailschema::Contract.parse(File.binread("publication-approval-0.1.json"))
22
+ contract.digest # => "sha-256:de91a49d..."
23
+ contract.description_errors(description) # => []
25
24
  ```
26
25
 
27
- Before sending a description, check it against every rule of the core and the contract, and store its digest or keep it rebuildable from the interaction:
26
+ A document that fails raises `Mailschema::InvalidDocument`, whose `errors` lists every reason as a JSON Pointer and a message, such as `"/expiresAt: must be later than issuedAt"`.
28
27
 
29
- ```ruby
30
- CONTRACT.description_errors(description) # => []
31
- Mailschema.digest(description) # => "sha-256:…"
32
- ```
33
-
34
- When a request arrives, check it in this order. No step applies an effect for a problem, and a refusal before the description is resolved and its digest matched leaves the request identifier unclaimed:
28
+ From a received email, read the one MAP part:
35
29
 
36
30
  ```ruby
37
- # Answer 415 unless Mailschema.json_request?(content_type). Under possession authority,
38
- # answer an unknown capability with a plain 404 and a lapsed one with a plain 410
39
- # before reading the body; under credential authority, authenticate the principal.
40
- request = Mailschema.parse(body) # raises Mailschema::InvalidDocument
41
- Mailschema.request_errors(request) # any errors: invalid-request
42
-
43
- # Resolve the description you issued for request["interactionId"]. Refuse an unknown
44
- # interaction, or a request whose descriptionDigest is not Mailschema.digest(description).
45
-
46
- # Request identifiers are claimed within your tenant: the account, or the capability.
47
- # If request["requestId"] is already claimed there, refuse another principal, and a
48
- # principal whose permission has been revoked, with an uncorrelated refused problem
49
- # (Mailschema.problem without request_id): the refusal says nothing about the request.
50
- # Answer any change to the request, compared by Mailschema.digest, with
51
- # idempotency-conflict. Otherwise settle the recorded result with Mailschema.settle and
52
- # return it, or expired-interaction once Mailschema.retain_until has passed. Nothing is
53
- # applied again.
54
-
55
- problem = CONTRACT.request_problem(description, request, now: Time.now)
56
- # problem.code is unsupported-type, unsupported-operation or expired-interaction,
57
- # with the title and detail Mailschema.problem takes. Then your own state:
58
- # already-decided, or stale-target.
59
-
60
- CONTRACT.input_errors(description, request)
61
- # => [{ "detail" => "...", "pointer" => "/feedback" }], a claimed invalid-request
31
+ result = Mailschema::Message.extract(Mail.new(raw_message))
32
+ result.status # => :valid, :invalid or :none
33
+ result.description # the parsed description when :valid
62
34
  ```
63
35
 
64
- Then apply the approval lifecycle or the effect, and record the result:
36
+ ## API
65
37
 
66
- ```ruby
67
- Mailschema.result(
68
- request,
69
- state: "accepted",
70
- target: description["target"],
71
- result_url: Mailschema.result_url(description, request["requestId"]),
72
- recorded_at: Time.now,
73
- output: { "feedbackRecorded" => true, "feedbackId" => "fb-1" }
74
- )
75
- ```
38
+ All under `Mailschema`:
76
39
 
77
- ## API
40
+ - `PROFILE`, `CONTEXT`, `DESCRIPTION_MEDIA_TYPE`, `DESCRIPTION_MAX_BYTES`, `CONTRACT_MAX_BYTES`, `MAX_DEPTH`, `EFFECTS`, `FORMATS` and `CAPABILITY_KINDS`.
41
+ - `parse(input, max_bytes)`: MAP JSON from text or bytes. It refuses invalid UTF-8, a byte order mark, duplicate member names (also after unescaping), lone surrogates, U+0000 and noncharacters, nesting deeper than 32, and numbers that are not finite tokens of at most 64 characters within the I-JSON range. It returns plain Hash, Array, String, Integer, Float, true, false and nil values; a number with no fractional part is an Integer.
42
+ - `canonicalize(value)` and `digest(value)`: the RFC 8785 form, and `sha-256:` with its hex SHA-256.
43
+ - `description_errors(value)` and `parse_description(input)`.
44
+ - `contract_errors(value)`, `Contract.new(value)` and `Contract.parse(input)`. A contract has `document` (frozen), `digest`, `id`, `version`, `operation(id)`, `details_errors(details)`, `input_errors(operation_id, input = nil)` and `description_errors(description)`.
45
+ - `implementation_errors(value)` and `parse_implementation(input)`.
46
+ - `Message.extract(mail)`: a `Message::Result` with `status` `:none`, `:invalid` (with a `reason`) or `:valid` (with the `description`). Pass a `Mail::Message` read from the received bytes; it never raises for untrusted input.
47
+ - `Message.part(description)`: the `Mail::Part` that carries a description, to place in a `multipart/related` entity after the readable content.
48
+ - `Authentication::Signature` and `Authentication.qualifying(raw_message:, signatures:, organizational_domain: nil)`: the first signature your DKIM verifier reported that qualifies the raw message, or nil. Alignment is strict unless you pass a callable that returns a domain's organizational domain.
78
49
 
79
- | Call | Does |
80
- | ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
81
- | `Mailschema.parse(json)` | Parses a MAP document as I-JSON within the core limits, integral numbers as Integers, or raises `Mailschema::InvalidDocument` |
82
- | `Mailschema.canonicalize(value)`, `Mailschema.digest(value)` | RFC 8785 canonical JSON, and `sha-256:` over it |
83
- | `Mailschema.map_errors(document)` | Checks a description, request, result or problem against the core schema |
84
- | `Mailschema.description_errors`, `Mailschema.request_errors` | Check a description or a request against its core definition only |
85
- | `Mailschema.result_errors`, `Mailschema.problem_errors` | Check a result or a problem against its core definition only |
86
- | `Mailschema::Contract.new(contract, request_schema, digest:, dependencies: {})` | Checks the contract's format and profile, refuses any contract but the one pinned by `digest`, checks its pinned dependencies and request schema digest, validates every schema against JSON Schema 2020-12 and compiles every validator once, or raises `Mailschema::InvalidContract`; `dependencies:` supplies, by URL, any pinned schema the gem does not bundle |
87
- | `contract.description_errors(description)` | Checks a description against every rule of the core and the contract |
88
- | `contract.request_problem(description, request, now:)` | The exact type, an offered operation the authority permits, and expiry, as a `Contract::Problem` or nil |
89
- | `contract.input_errors(description, request)` | Checks an operation's input and field bindings, with JSON Pointers into `input` |
90
- | `contract.result_errors(result)` | Checks a result against the core, the declared output schema and the declared reason |
91
- | `contract.type_reference`, `contract.operation(id)`, `contract.decision?(id)` | The type reference descriptions and requests name, one of its operations, and whether completing it decides the interaction |
92
- | `contract.id`, `contract.version`, `contract.digest`, `contract.document`, `contract.request_schema` | The contract's identity, and frozen copies of the documents it verified |
93
- | `Mailschema.result`, `Mailschema.transition`, `Mailschema.problem` | Build results, state transitions and problems with their correlation members, or raise `ArgumentError` rather than return a document the core refuses. A problem stays within the core limits: a title of at most 240 characters, a detail cut at 4000, and as many of the first 100 input errors, each bounded, as fit in 64 KiB as `JSON.generate` writes them, so send documents written that way |
94
- | `Mailschema.result_url`, `Mailschema.result_status`, `Mailschema.request_id?` | The result resource of a request, a result's HTTP status, and whether a value is a request identifier, the only thing a result URL names |
95
- | `Mailschema.json_request?(content_type)` | Whether a `Content-Type` admits a request rather than a 415 |
96
- | `Mailschema::DESCRIPTION_MEDIA_TYPE`, `Mailschema.description_part?(media_type, profile_parameter)` | The `Content-Type` of the part that carries a description, and whether a designated part is labelled with this profile; a client processes the one such part outside any attached message and ignores every other structured part |
97
- | `Mailschema.retain_until`, `Mailschema.settle`, `Mailschema.reached?` | Result retention, the expiry of an undecided approval, and whether a deadline has passed |
98
- | `Mailschema.capability(description)`, `Mailschema.written_path(url)` | The possession capability: the last segment of the execution URL's path as written |
99
- | `Mailschema.artifact(url)`, `Mailschema.document(url)` | The bundled core artifacts, as exact bytes or as a fresh parsed copy |
100
-
101
- Type rules a contract cannot express, such as a meeting slot being one of the slots offered, belong to each type's implementation.
102
-
103
- ## Bundled artifacts
104
-
105
- The gem carries the MAP 0.2 core schema, its JSON-LD context, the type contract format and the form fields block, and the Registry contribution schema that every MailSchema package binds. Each is byte-identical to the file [mailschema.org](https://mailschema.org) publishes. The gem does not validate contributions; the JavaScript and Python packages do. Type contracts are not bundled: an implementation vendors the contracts it supports, and `Mailschema::Contract` verifies them by digest.
106
-
107
- JSON Schema validation uses `json_schemer` with ECMA-262 regular expressions. Lexical forms such as date-times and addresses are core schema patterns, so `format` is never asserted. References resolve only against pinned schemas, never over the network.
50
+ Every `*_errors` method returns an array of strings, empty when the value is valid. Schemas are validated with `json_schemer` (JSON Schema 2020-12, ECMA-262 regular expressions) against the profile artifacts the gem bundles.
108
51
 
109
52
  ## Links
110
53
 
111
- - [Specification](https://mailschema.org/specification/profile)
112
- - [Source](https://github.com/mailschema/ruby)
113
-
114
- MIT License.
54
+ - Specification: https://mailschema.org
55
+ - Source and issues: https://github.com/mailschema/ruby
@@ -0,0 +1,50 @@
1
+ {
2
+ "@context": {
3
+ "@version": 1.1,
4
+ "@protected": true,
5
+ "@vocab": "https://mailschema.org/vocab/map/0.3#",
6
+ "MailAction": "https://mailschema.org/vocab/map/0.3#MailAction",
7
+ "profile": {
8
+ "@id": "profile",
9
+ "@type": "@id"
10
+ },
11
+ "type": {
12
+ "@id": "type",
13
+ "@type": "@json"
14
+ },
15
+ "service": {
16
+ "@id": "service",
17
+ "@type": "@json"
18
+ },
19
+ "subject": {
20
+ "@id": "subject",
21
+ "@type": "@json"
22
+ },
23
+ "terms": {
24
+ "@id": "terms",
25
+ "@type": "@json"
26
+ },
27
+ "details": {
28
+ "@id": "details",
29
+ "@type": "@json"
30
+ },
31
+ "operations": {
32
+ "@id": "operations",
33
+ "@type": "@json"
34
+ },
35
+ "human": {
36
+ "@id": "human",
37
+ "@type": "@json"
38
+ },
39
+ "recipient": "recipient",
40
+ "inLanguage": "inLanguage",
41
+ "issuedAt": {
42
+ "@id": "issuedAt",
43
+ "@type": "http://www.w3.org/2001/XMLSchema#dateTime"
44
+ },
45
+ "expiresAt": {
46
+ "@id": "expiresAt",
47
+ "@type": "http://www.w3.org/2001/XMLSchema#dateTime"
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,206 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://mailschema.org/artifacts/map-0.3/schemas/contract.schema.json",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": [
7
+ "id",
8
+ "version",
9
+ "profile",
10
+ "name",
11
+ "summary",
12
+ "requirements",
13
+ "detailsSchema",
14
+ "operations"
15
+ ],
16
+ "properties": {
17
+ "id": {
18
+ "type": "string",
19
+ "minLength": 1,
20
+ "format": "uri",
21
+ "pattern": "^https://",
22
+ "maxLength": 2048
23
+ },
24
+ "version": {
25
+ "type": "string",
26
+ "minLength": 1,
27
+ "maxLength": 64
28
+ },
29
+ "profile": {
30
+ "const": "https://mailschema.org/profiles/map/0.3"
31
+ },
32
+ "name": {
33
+ "type": "string",
34
+ "minLength": 1,
35
+ "maxLength": 2048
36
+ },
37
+ "summary": {
38
+ "type": "string",
39
+ "minLength": 1,
40
+ "maxLength": 2048
41
+ },
42
+ "requirements": {
43
+ "type": "array",
44
+ "items": {
45
+ "type": "string",
46
+ "minLength": 1,
47
+ "maxLength": 2048
48
+ },
49
+ "minItems": 1,
50
+ "maxItems": 64
51
+ },
52
+ "detailsSchema": {
53
+ "type": "object"
54
+ },
55
+ "operations": {
56
+ "type": "array",
57
+ "items": {
58
+ "type": "object",
59
+ "additionalProperties": false,
60
+ "required": [
61
+ "id",
62
+ "name",
63
+ "semantics",
64
+ "effects",
65
+ "bindings",
66
+ "actors",
67
+ "exactTerms",
68
+ "inputSchema",
69
+ "outcomes"
70
+ ],
71
+ "properties": {
72
+ "id": {
73
+ "type": "string",
74
+ "minLength": 1,
75
+ "pattern": "^[a-z][a-z0-9-]{0,63}$"
76
+ },
77
+ "name": {
78
+ "type": "string",
79
+ "minLength": 1,
80
+ "maxLength": 2048
81
+ },
82
+ "semantics": {
83
+ "type": "string",
84
+ "minLength": 1,
85
+ "maxLength": 2048
86
+ },
87
+ "effects": {
88
+ "type": "array",
89
+ "items": {
90
+ "type": "string",
91
+ "minLength": 1,
92
+ "format": "uri",
93
+ "pattern": "^https://"
94
+ },
95
+ "minItems": 1,
96
+ "maxItems": 32,
97
+ "uniqueItems": true
98
+ },
99
+ "bindings": {
100
+ "type": "array",
101
+ "items": {
102
+ "enum": [
103
+ "credential",
104
+ "capability"
105
+ ]
106
+ },
107
+ "minItems": 1,
108
+ "maxItems": 2,
109
+ "uniqueItems": true
110
+ },
111
+ "actors": {
112
+ "type": "array",
113
+ "items": {
114
+ "enum": [
115
+ "human",
116
+ "agent"
117
+ ]
118
+ },
119
+ "minItems": 1,
120
+ "maxItems": 2,
121
+ "uniqueItems": true
122
+ },
123
+ "exactTerms": {
124
+ "type": "boolean"
125
+ },
126
+ "inputSchema": {
127
+ "type": [
128
+ "object",
129
+ "null"
130
+ ]
131
+ },
132
+ "outcomes": {
133
+ "type": "object",
134
+ "minProperties": 1,
135
+ "propertyNames": {
136
+ "pattern": "^[a-z][a-z0-9-]{0,63}$"
137
+ },
138
+ "additionalProperties": {
139
+ "type": "string",
140
+ "minLength": 1,
141
+ "maxLength": 2048
142
+ }
143
+ },
144
+ "capability": {
145
+ "type": "object",
146
+ "additionalProperties": false,
147
+ "required": [
148
+ "kind",
149
+ "maxLifetimeSeconds"
150
+ ],
151
+ "properties": {
152
+ "kind": {
153
+ "enum": [
154
+ "refusal",
155
+ "protective-report",
156
+ "address-confirmation"
157
+ ]
158
+ },
159
+ "maxLifetimeSeconds": {
160
+ "type": "integer",
161
+ "minimum": 1,
162
+ "maximum": 604800
163
+ }
164
+ }
165
+ }
166
+ },
167
+ "allOf": [
168
+ {
169
+ "if": {
170
+ "properties": {
171
+ "bindings": {
172
+ "contains": {
173
+ "const": "capability"
174
+ },
175
+ "type": "array"
176
+ }
177
+ }
178
+ },
179
+ "then": {
180
+ "required": [
181
+ "capability"
182
+ ],
183
+ "properties": {
184
+ "inputSchema": {
185
+ "type": "null"
186
+ },
187
+ "exactTerms": {
188
+ "const": true
189
+ }
190
+ }
191
+ },
192
+ "else": {
193
+ "not": {
194
+ "required": [
195
+ "capability"
196
+ ]
197
+ }
198
+ }
199
+ }
200
+ ]
201
+ },
202
+ "minItems": 1,
203
+ "maxItems": 32
204
+ }
205
+ }
206
+ }
@@ -0,0 +1,210 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://mailschema.org/artifacts/map-0.3/schemas/core.schema.json",
4
+ "title": "MAP 0.3 description shape; semantic checks in Core are additionally required",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": [
8
+ "@context",
9
+ "@type",
10
+ "@id",
11
+ "profile",
12
+ "type",
13
+ "issuedAt",
14
+ "expiresAt",
15
+ "inLanguage",
16
+ "service",
17
+ "recipient",
18
+ "subject",
19
+ "terms",
20
+ "details",
21
+ "operations",
22
+ "human"
23
+ ],
24
+ "properties": {
25
+ "@context": {
26
+ "const": "https://mailschema.org/contexts/map-0.3.jsonld"
27
+ },
28
+ "@type": {
29
+ "const": "MailAction"
30
+ },
31
+ "@id": {
32
+ "type": "string",
33
+ "minLength": 1,
34
+ "format": "uri",
35
+ "maxLength": 2048
36
+ },
37
+ "profile": {
38
+ "const": "https://mailschema.org/profiles/map/0.3"
39
+ },
40
+ "type": {
41
+ "type": "object",
42
+ "additionalProperties": false,
43
+ "required": [
44
+ "id",
45
+ "version",
46
+ "contractDigest"
47
+ ],
48
+ "properties": {
49
+ "id": {
50
+ "type": "string",
51
+ "minLength": 1,
52
+ "format": "uri",
53
+ "pattern": "^https://",
54
+ "maxLength": 2048
55
+ },
56
+ "version": {
57
+ "type": "string",
58
+ "minLength": 1,
59
+ "maxLength": 64
60
+ },
61
+ "contractDigest": {
62
+ "type": "string",
63
+ "minLength": 1,
64
+ "pattern": "^sha-256:[0-9a-f]{64}$"
65
+ }
66
+ }
67
+ },
68
+ "issuedAt": {
69
+ "type": "string",
70
+ "minLength": 1,
71
+ "format": "date-time",
72
+ "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])T([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\dZ$"
73
+ },
74
+ "expiresAt": {
75
+ "type": "string",
76
+ "minLength": 1,
77
+ "format": "date-time",
78
+ "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])T([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\dZ$"
79
+ },
80
+ "inLanguage": {
81
+ "type": "string",
82
+ "minLength": 1,
83
+ "maxLength": 255
84
+ },
85
+ "service": {
86
+ "type": "object",
87
+ "additionalProperties": false,
88
+ "required": [
89
+ "id"
90
+ ],
91
+ "properties": {
92
+ "id": {
93
+ "type": "string",
94
+ "minLength": 1,
95
+ "format": "uri",
96
+ "pattern": "^https://(?![0-9.]+(:[0-9]+)?$)[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(:[0-9]{1,5})?$",
97
+ "maxLength": 2048
98
+ },
99
+ "tenant": {
100
+ "type": "string",
101
+ "minLength": 1,
102
+ "format": "uri",
103
+ "maxLength": 2048
104
+ }
105
+ }
106
+ },
107
+ "recipient": {
108
+ "type": "string",
109
+ "minLength": 1,
110
+ "maxLength": 320
111
+ },
112
+ "subject": {
113
+ "type": "object",
114
+ "additionalProperties": false,
115
+ "required": [
116
+ "id",
117
+ "title"
118
+ ],
119
+ "properties": {
120
+ "id": {
121
+ "type": "string",
122
+ "minLength": 1,
123
+ "format": "uri",
124
+ "maxLength": 2048
125
+ },
126
+ "title": {
127
+ "type": "string",
128
+ "minLength": 1,
129
+ "maxLength": 2048
130
+ }
131
+ }
132
+ },
133
+ "terms": {
134
+ "type": "object",
135
+ "additionalProperties": false,
136
+ "required": [
137
+ "id",
138
+ "version"
139
+ ],
140
+ "properties": {
141
+ "id": {
142
+ "type": "string",
143
+ "minLength": 1,
144
+ "format": "uri",
145
+ "maxLength": 2048
146
+ },
147
+ "version": {
148
+ "type": "string",
149
+ "minLength": 1,
150
+ "pattern": "^[ -~]+$",
151
+ "maxLength": 1024
152
+ }
153
+ }
154
+ },
155
+ "details": {
156
+ "type": "object"
157
+ },
158
+ "operations": {
159
+ "type": "array",
160
+ "items": {
161
+ "type": "object",
162
+ "additionalProperties": false,
163
+ "required": [
164
+ "id"
165
+ ],
166
+ "properties": {
167
+ "id": {
168
+ "type": "string",
169
+ "minLength": 1,
170
+ "pattern": "^[a-z][a-z0-9-]{0,63}$"
171
+ },
172
+ "capability": {
173
+ "type": "object",
174
+ "additionalProperties": false,
175
+ "required": [
176
+ "url"
177
+ ],
178
+ "properties": {
179
+ "url": {
180
+ "type": "string",
181
+ "minLength": 1,
182
+ "format": "uri",
183
+ "pattern": "^https://[^/?#@]+([/?][^#]*)?$",
184
+ "maxLength": 4096
185
+ }
186
+ }
187
+ }
188
+ }
189
+ },
190
+ "minItems": 1,
191
+ "maxItems": 32
192
+ },
193
+ "human": {
194
+ "type": "object",
195
+ "additionalProperties": false,
196
+ "required": [
197
+ "url"
198
+ ],
199
+ "properties": {
200
+ "url": {
201
+ "type": "string",
202
+ "minLength": 1,
203
+ "format": "uri",
204
+ "pattern": "^https://[^/?#@]+([/?][^#]*)?$",
205
+ "maxLength": 4096
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }