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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -7
- data/README.md +29 -88
- data/artifacts/context.jsonld +50 -0
- data/artifacts/contract.schema.json +206 -0
- data/artifacts/core.schema.json +210 -0
- data/artifacts/implementation.schema.json +173 -0
- data/artifacts/profile.json +27 -0
- data/lib/mailschema/artifacts.rb +32 -75
- data/lib/mailschema/authentication.rb +77 -0
- data/lib/mailschema/contract.rb +184 -281
- data/lib/mailschema/description.rb +56 -0
- data/lib/mailschema/formats.rb +77 -0
- data/lib/mailschema/header.rb +75 -0
- data/lib/mailschema/implementation.rb +27 -0
- data/lib/mailschema/jcs.rb +11 -10
- data/lib/mailschema/json.rb +190 -0
- data/lib/mailschema/message.rb +127 -12
- data/lib/mailschema/origin.rb +18 -0
- data/lib/mailschema/patterns.rb +173 -0
- data/lib/mailschema/subschemas.rb +70 -0
- data/lib/mailschema/version.rb +1 -1
- data/lib/mailschema.rb +16 -26
- data/sig/mailschema.rbs +76 -68
- metadata +29 -54
- data/contexts/map-0.2.jsonld +0 -91
- data/lib/mailschema/capability.rb +0 -41
- data/lib/mailschema/document.rb +0 -79
- data/lib/mailschema/documents.rb +0 -127
- data/lib/mailschema/forms.rb +0 -41
- data/lib/mailschema/http.rb +0 -25
- data/lib/mailschema/limits.rb +0 -56
- data/lib/mailschema/pointer.rb +0 -46
- data/lib/mailschema/references.rb +0 -62
- data/lib/mailschema/schema_walk.rb +0 -29
- data/schemas/contribution.schema.json +0 -874
- data/schemas/forms-0.1.schema.json +0 -329
- data/schemas/map-0.2.schema.json +0 -1033
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d40b5366532ded5404a82499db609a8e30e4edd70df68befef93c6c227cfb461
|
|
4
|
+
data.tar.gz: bd03b7643aa9589044fcf10fa2bf63b35b3856f370198af3663c2beb6343280d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8195ac0b45dfc6ee758e0c25b8e46195f4768b07ebde12debf8629377c10e97536687dcb2bf0848c94d34921803534dbb1611b3a3ace46eb6f0f850d280c0be2
|
|
7
|
+
data.tar.gz: 42b7343562247873c5c01b96a58477e0775d19274a7ce94c740c71e190bb639a5f476809446d48043f44599d8e80f855f918cf6447e229c450c6787459d081ff
|
data/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [
|
|
3
|
+
## [0.3.0] - 2026-10-07
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
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.
|
|
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
|
|
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
|
-
##
|
|
13
|
+
## Example
|
|
14
14
|
|
|
15
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
#
|
|
39
|
-
#
|
|
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
|
-
|
|
36
|
+
## API
|
|
65
37
|
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
112
|
-
-
|
|
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
|
+
}
|