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
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailschema
4
+ # The regular expressions a contract's schemas may use: the subset that ECMA-262 (with the u
5
+ # flag) and other engines read alike, so every implementation enforces the same contract.
6
+ class Patterns
7
+ SYNTAX_CHARACTERS = "^$\\.*+?()[]{}|/"
8
+ CONTROL_ESCAPES = { "t" => 0x09, "n" => 0x0a, "f" => 0x0c, "r" => 0x0d }.freeze
9
+ QUANTIFIERS = "*+?{"
10
+ BOUNDS = /\A\{([0-9]{1,4})(?:(,)([0-9]{1,4})?)?\}/
11
+ # A class member that is an unescaped hyphen.
12
+ HYPHEN = :hyphen
13
+
14
+ # Why a pattern is outside the portable subset, or nil. The subset is printable ASCII
15
+ # text, with any other code point written as \uXXXX. It has literal characters and escaped
16
+ # syntax characters; \t, \n, \f, \r and \d, the ASCII digits; non-empty classes of literal
17
+ # or escaped members and ranges, with a literal hyphen only first or last; (?: groups;
18
+ # alternation; ^ and $; and one *, +, ?, {n}, {n,} or {n,m} after an atom. Other class
19
+ # escapes and the dot match different characters in different engines, and brackets, && or
20
+ # a stray brace inside a class, or a quantifier on a quantifier, are read differently or
21
+ # refused by one of them. ^ and $ anchor the whole value, as in ECMA-262; an engine whose
22
+ # anchors also match at line breaks must read them so.
23
+ def self.unportable(pattern) = new(pattern).problem
24
+
25
+ def initialize(pattern)
26
+ @pattern = pattern
27
+ @at = 0
28
+ end
29
+
30
+ def problem
31
+ return "a character outside printable ASCII" unless /\A[\x20-\x7e]*\z/.match?(@pattern)
32
+
33
+ disjunction(0)
34
+ end
35
+
36
+ private
37
+
38
+ def disjunction(depth)
39
+ quantifiable = false
40
+ while @at < @pattern.length
41
+ char = @pattern[@at]
42
+ return depth.zero? ? "an unmatched )" : nil if char == ")"
43
+
44
+ problem, quantifiable = QUANTIFIERS.include?(char) ? quantified(char, quantifiable) : term(char, depth)
45
+ return problem if problem
46
+ end
47
+ end
48
+
49
+ # The problem with the term at `char`, or nil and whether a quantifier may follow it.
50
+ def term(char, depth)
51
+ case char
52
+ when "|", "^", "$"
53
+ @at += 1
54
+ [nil, false]
55
+ when "(" then [group(depth), true]
56
+ when "[" then [character_class, true]
57
+ when "\\"
58
+ code = escape(false)
59
+ [(code if code.is_a?(String)), true]
60
+ when "." then ["an unescaped dot"]
61
+ when "]", "}" then ["an unescaped #{char}"]
62
+ else
63
+ @at += 1
64
+ [nil, true]
65
+ end
66
+ end
67
+
68
+ def group(depth)
69
+ return "a group other than (?:" unless @pattern[@at, 3] == "(?:"
70
+
71
+ @at += 3
72
+ problem = disjunction(depth + 1)
73
+ return problem if problem
74
+ return "an unclosed group" unless @pattern[@at] == ")"
75
+
76
+ @at += 1
77
+ nil
78
+ end
79
+
80
+ def quantified(char, quantifiable)
81
+ return [char == "{" ? "an unescaped {" : "a quantifier with nothing to repeat"] unless quantifiable
82
+
83
+ if char == "{"
84
+ bounds = BOUNDS.match(@pattern[@at..])
85
+ return ["a malformed {n,m} quantifier"] unless bounds
86
+ return ["a {n,m} quantifier with m below n"] if bounds[3] && bounds[3].to_i < bounds[1].to_i
87
+
88
+ @at += bounds[0].length
89
+ else
90
+ @at += 1
91
+ end
92
+ following = @pattern[@at]
93
+ [("a quantifier on a quantifier" if following && QUANTIFIERS.include?(following)), false]
94
+ end
95
+
96
+ # One escaped code point, -1 for the digit class outside a class, or a problem.
97
+ def escape(in_class)
98
+ following = @pattern[@at + 1]
99
+ return "a trailing backslash" if following.nil?
100
+ return unicode_escape if following == "u"
101
+
102
+ @at += 2
103
+ return -1 if following == "d" && !in_class
104
+ return CONTROL_ESCAPES[following] if CONTROL_ESCAPES.key?(following)
105
+ return following.ord if SYNTAX_CHARACTERS.include?(following) || (in_class && following == "-")
106
+
107
+ "the escape \\#{following}"
108
+ end
109
+
110
+ def unicode_escape
111
+ digits = @pattern[@at + 2, 4].to_s
112
+ return "a malformed \\u escape" unless /\A\h{4}\z/.match?(digits)
113
+
114
+ code = digits.hex
115
+ return "a surrogate \\u escape" if code.between?(0xd800, 0xdfff)
116
+
117
+ @at += 6
118
+ code
119
+ end
120
+
121
+ def character_class
122
+ @at += @pattern[@at + 1] == "^" ? 2 : 1
123
+ # Each member's code point, or HYPHEN.
124
+ members = []
125
+ until @pattern[@at] == "]"
126
+ problem = class_member(members)
127
+ return problem if problem
128
+ end
129
+ @at += 1
130
+ return "an empty character class" if members.empty?
131
+
132
+ range_problem(members)
133
+ end
134
+
135
+ def class_member(members)
136
+ char = @pattern[@at]
137
+ return "an unclosed character class" if char.nil?
138
+ return "a bracket inside a class" if char == "["
139
+ return "&& inside a class" if char == "&" && @pattern[@at + 1] == "&"
140
+
141
+ if char == "\\"
142
+ code = escape(true)
143
+ return code if code.is_a?(String)
144
+
145
+ members << code
146
+ else
147
+ members << (char == "-" ? HYPHEN : char.ord)
148
+ @at += 1
149
+ end
150
+ nil
151
+ end
152
+
153
+ def range_problem(members)
154
+ last = members.size - 1
155
+ index = 0
156
+ while index <= last
157
+ low = members[index]
158
+ if low == HYPHEN
159
+ return "an ambiguous hyphen in a class" unless [0, last].include?(index)
160
+ elsif members[index + 1] == HYPHEN && index + 2 <= last
161
+ high = members[index + 2]
162
+ return "an ambiguous hyphen in a class" if high == HYPHEN
163
+ return "a class range out of order" if high < low
164
+
165
+ index += 2
166
+ return "an ambiguous hyphen in a class" if members[index + 1] == HYPHEN && index + 1 != last
167
+ end
168
+ index += 1
169
+ end
170
+ end
171
+ end
172
+ private_constant :Patterns
173
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailschema
4
+ # The schemas within a JSON Schema 2020-12 schema, by the JSON Pointers that name them.
5
+ module Subschemas
6
+ SCHEMA = %w[additionalProperties propertyNames items contains not if then else unevaluatedItems
7
+ unevaluatedProperties].freeze
8
+ SCHEMA_LISTS = %w[allOf anyOf oneOf prefixItems].freeze
9
+ SCHEMA_MAPS = %w[properties patternProperties $defs dependentSchemas].freeze
10
+ # The keywords that apply their subschemas to the value itself, not to a member or item.
11
+ IN_PLACE = %w[not if then else allOf anyOf oneOf dependentSchemas].freeze
12
+
13
+ class << self
14
+ # Every schema within a schema, object or boolean, with its JSON Pointer, the schema
15
+ # itself first.
16
+ def all(schema, pointer)
17
+ return [[pointer, schema]] if [true, false].include?(schema)
18
+ return [] unless schema.is_a?(Hash)
19
+
20
+ [[pointer, schema], *children(schema, pointer).flat_map { |_keyword, at, item| all(item, at) }]
21
+ end
22
+
23
+ # Whether applying the schema can never finish: from its root, a chain of $ref and
24
+ # in-place keywords returns to a schema without moving into a member or item.
25
+ def endless?(schema)
26
+ links = all(schema, "").to_h { |at, node| [at, node.is_a?(Hash) ? links(at, node) : []] }
27
+ reachable = [""]
28
+ seen = Set[""]
29
+ reachable.each { |at| links.fetch(at, []).map(&:first).each { |to| reachable << to if seen.add?(to) } }
30
+ state = {}
31
+ visit = lambda do |at|
32
+ return state[at] == :open if state.key?(at)
33
+
34
+ state[at] = :open
35
+ looped = links.fetch(at, []).any? { |to, in_place| in_place && visit.call(to) }
36
+ state[at] = :done
37
+ looped
38
+ end
39
+ reachable.any? { |at| visit.call(at) }
40
+ end
41
+
42
+ private
43
+
44
+ # The values a schema object holds where schemas belong, as [keyword, pointer, value].
45
+ def children(schema, pointer)
46
+ lists = SCHEMA_LISTS.select { |keyword| schema[keyword].is_a?(Array) }
47
+ maps = SCHEMA_MAPS.select { |keyword| schema[keyword].is_a?(Hash) }
48
+ [
49
+ *SCHEMA.filter_map { |keyword| [keyword, "#{pointer}/#{keyword}", schema[keyword]] if schema.key?(keyword) },
50
+ *lists.flat_map do |keyword|
51
+ schema[keyword].each_with_index.map { |item, index| [keyword, "#{pointer}/#{keyword}/#{index}", item] }
52
+ end,
53
+ *maps.flat_map do |keyword|
54
+ schema[keyword].map { |name, item| [keyword, "#{pointer}/#{keyword}/#{escape(name)}", item] }
55
+ end
56
+ ]
57
+ end
58
+
59
+ # Where a schema object sends its value: [pointer, whether it is the same value].
60
+ def links(at, node)
61
+ applied = children(node, at).reject { |keyword, _at, _item| keyword == "$defs" }
62
+ .map { |keyword, to, _item| [to, IN_PLACE.include?(keyword)] }
63
+ node.key?("$ref") ? [*applied, [node["$ref"][1..], true]] : applied
64
+ end
65
+
66
+ def escape(name) = name.gsub("~", "~0").gsub("/", "~1")
67
+ end
68
+ end
69
+ private_constant :Subschemas
70
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mailschema
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/mailschema.rb CHANGED
@@ -1,41 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bigdecimal"
4
3
  require "digest"
5
4
  require "json"
6
5
  require "json_schemer"
6
+ require "mail"
7
+ require "strscan"
7
8
  require "time"
9
+ require "uri"
8
10
 
9
11
  require_relative "mailschema/version"
10
- require_relative "mailschema/limits"
11
12
  require_relative "mailschema/jcs"
12
- require_relative "mailschema/document"
13
+ require_relative "mailschema/json"
14
+ require_relative "mailschema/formats"
13
15
  require_relative "mailschema/artifacts"
14
- require_relative "mailschema/pointer"
15
- require_relative "mailschema/schema_walk"
16
- require_relative "mailschema/references"
17
- require_relative "mailschema/forms"
18
- require_relative "mailschema/capability"
16
+ require_relative "mailschema/origin"
17
+ require_relative "mailschema/patterns"
18
+ require_relative "mailschema/subschemas"
19
+ require_relative "mailschema/description"
19
20
  require_relative "mailschema/contract"
20
- require_relative "mailschema/documents"
21
- require_relative "mailschema/http"
21
+ require_relative "mailschema/implementation"
22
+ require_relative "mailschema/header"
22
23
  require_relative "mailschema/message"
24
+ require_relative "mailschema/authentication"
23
25
 
24
- # Mail Action Protocol 0.2 tooling. The module parses,
25
- # canonicalizes, digests and validates MAP documents, checks the type contracts an
26
- # implementation vendors, and builds results and problems. It does not establish
27
- # endpoint trust, verify email authentication, grant authority or send email.
26
+ # Mail Action Protocol 0.3 processing: strict MAP JSON, RFC 8785 digests, descriptions, type
27
+ # contracts and implementation records, the MAP part of an email, and the qualifying DKIM
28
+ # signature rules. It does not verify DKIM, resolve DNS, fetch anything named in an email,
29
+ # grant authority or send email.
28
30
  module Mailschema
29
- # RFC 8785 canonical JSON of a value.
30
- def self.canonicalize(value) = JCS.canonicalize(value)
31
-
32
- # `sha-256:` and the SHA-256 of the RFC 8785 canonical form: the digest MAP uses
33
- # for descriptions, contracts and pinned schemas.
34
- def self.digest(value) = "sha-256:#{Digest::SHA256.hexdigest(JCS.canonicalize(value))}"
35
-
36
- # Whether a deadline, a core date-time plus `after` seconds, has been reached.
37
- # Anything but a core date-time counts as reached, so a mistake fails closed.
38
- def self.reached?(now, deadline, after: 0)
39
- !(DATE_TIME.valid?(deadline) && now < Time.iso8601(deadline) + after)
40
- end
41
31
  end
data/sig/mailschema.rbs CHANGED
@@ -1,92 +1,100 @@
1
- # Mail Action Protocol 0.2 tooling.
1
+ # Mail Action Protocol 0.3 processing.
2
2
  module Mailschema
3
3
  type json = Hash[String, untyped] | Array[untyped] | String | Integer | Float | bool | nil
4
4
  type document = Hash[String, untyped]
5
- type input_error = Hash[String, String]
5
+ type capability_kind = { effects: Array[String], max_lifetime_seconds: Integer }
6
6
 
7
7
  VERSION: String
8
8
 
9
9
  PROFILE: String
10
- CORE_SCHEMA: String
11
- FORMS_SCHEMA: String
12
- CONTRACT_FORMAT: String
13
10
  CONTEXT: String
14
- CONTRIBUTION_SCHEMA: String
15
- ARTIFACTS: Hash[String, String]
16
- APPROVAL_REASONS: Array[String]
17
- PROBLEM_STATUS: Hash[String, Integer]
18
- PROBLEM_TYPES: String
19
- NON_TERMINAL_STATES: Array[String]
20
- MAX_BYTES: Integer
11
+ DESCRIPTION_MEDIA_TYPE: String
12
+ DESCRIPTION_MAX_BYTES: Integer
13
+ CONTRACT_MAX_BYTES: Integer
21
14
  MAX_DEPTH: Integer
22
- EXACT_NUMBER: Integer
23
-
24
- class Error < StandardError
25
- end
15
+ EFFECTS: String
16
+ FORMATS: Array[String]
17
+ CAPABILITY_KINDS: Hash[String, capability_kind]
26
18
 
27
- class InvalidDocument < Error
28
- end
19
+ # A document that is not MAP JSON, or not the document it claims to be.
20
+ class InvalidDocument < StandardError
21
+ attr_reader errors: Array[String]
29
22
 
30
- class InvalidContract < Error
23
+ def initialize: (String message, ?Array[String] errors) -> void
31
24
  end
32
25
 
33
- def self.parse: (String json) -> json
26
+ def self.parse: (String input, Integer max_bytes) -> json
34
27
  def self.canonicalize: (json value) -> String
35
28
  def self.digest: (json value) -> String
36
- def self.reached?: (Time now, untyped deadline, ?after: Numeric) -> bool
37
-
38
- def self.artifact: (String url) -> String
39
- def self.document: (String url) -> document
40
-
41
- def self.map_errors: (untyped document) -> Array[String]
42
- def self.description_errors: (untyped description) -> Array[String]
43
- def self.request_errors: (untyped request) -> Array[String]
44
- def self.result_errors: (untyped result) -> Array[String]
45
- def self.problem_errors: (untyped problem) -> Array[String]
46
- def self.request_id?: (untyped value) -> bool
47
-
48
- def self.capability: (document description) -> String
49
- def self.written_path: (String? url) -> String
50
-
51
- def self.result: (document request, state: String, target: document, result_url: String, recorded_at: Time,
52
- ?output: document, ?reason: String?, ?approval_url: String?, ?actor: String?) -> document
53
- def self.transition: (document result, state: String, recorded_at: Time, ?reason: String?, ?output: document) -> document
54
- def self.problem: (String code, title: String, detail: String, ?request_id: String?, ?interaction_id: String?,
55
- ?result_url: String?, ?target: document?, ?errors: Array[input_error]?) -> document
56
- def self.result_status: (document result) -> Integer
57
- def self.json_request?: (String? content_type) -> bool
58
- DESCRIPTION_MEDIA_TYPE: String
59
- def self.description_part?: (String? media_type, String? profile_parameter) -> bool
60
- def self.result_url: (document description, String request_id) -> String
61
- def self.retain_until: (document description, Time recorded_at) -> Time
62
- def self.settle: (document result, document description, Time now) -> document?
63
29
 
64
- # A type contract an implementation vendors, verified by digest and compiled once.
65
- class Contract
66
- # A MAP problem a request earns before the service's own state is consulted.
67
- class Problem < Data
68
- attr_reader code: String
69
- attr_reader title: String
70
- attr_reader detail: String
71
-
72
- def self.new: (String code, String title, String detail) -> instance
73
- | (code: String, title: String, detail: String) -> instance
74
- end
30
+ def self.description_errors: (untyped value) -> Array[String]
31
+ def self.parse_description: (String input) -> document
75
32
 
33
+ def self.contract_errors: (untyped value) -> Array[String]
34
+
35
+ def self.implementation_errors: (untyped value) -> Array[String]
36
+ def self.parse_implementation: (String input) -> document
37
+
38
+ # A valid contract with its digest and compiled schemas.
39
+ class Contract
76
40
  attr_reader document: document
77
41
  attr_reader digest: String
78
- attr_reader request_schema: document
79
42
 
80
- def initialize: (document contract, document request_schema, digest: String,
81
- ?dependencies: Hash[String, document]) -> void
43
+ def self.parse: (String input) -> Contract
44
+
45
+ def initialize: (untyped value) -> void
82
46
  def id: () -> String
83
47
  def version: () -> String
84
- def type_reference: () -> Hash[String, String]
85
- def operation: (String? id) -> document?
86
- def decision?: (String id) -> bool
87
- def description_errors: (document description) -> Array[String]
88
- def request_problem: (document description, document request, now: Time) -> Problem?
89
- def input_errors: (document description, document request) -> Array[input_error]
90
- def result_errors: (document result) -> Array[String]
48
+ def operation: (String id) -> document?
49
+ def details_errors: (untyped details) -> Array[String]
50
+ def input_errors: (String operation_id, ?untyped input) -> Array[String]
51
+ def description_errors: (untyped description) -> Array[String]
52
+ end
53
+
54
+ # The MAP part of an email.
55
+ module Message
56
+ class Result < Data
57
+ attr_reader status: :none | :invalid | :valid
58
+ attr_reader reason: Symbol?
59
+ attr_reader description: document?
60
+
61
+ def self.new: (Symbol status, Symbol? reason, document? description) -> instance
62
+ | (status: Symbol, reason: Symbol?, description: document?) -> instance
63
+ end
64
+
65
+ NONE: Result
66
+
67
+ # `mail` is a Mail::Message read from the received bytes.
68
+ def self.extract: (untyped mail) -> Result
69
+
70
+ # Returns a Mail::Part.
71
+ def self.part: (document description) -> untyped
72
+ end
73
+
74
+ # Core "Qualifying message authentication" over a DKIM verifier's facts.
75
+ module Authentication
76
+ class Signature < Data
77
+ attr_reader pass: bool
78
+ attr_reader domain: String
79
+ attr_reader body_length_limited: bool
80
+ attr_reader algorithm: String
81
+ attr_reader key_type: String
82
+ attr_reader key_bits: Integer
83
+ attr_reader signed_headers: Array[String]
84
+
85
+ def self.new: (bool pass, String domain, bool body_length_limited, String algorithm, String key_type,
86
+ Integer key_bits, Array[String] signed_headers) -> instance
87
+ | (pass: bool, domain: String, body_length_limited: bool, algorithm: String, key_type: String,
88
+ key_bits: Integer, signed_headers: Array[String]) -> instance
89
+
90
+ def pass?: () -> bool
91
+ def body_length_limited?: () -> bool
92
+ end
93
+
94
+ REQUIRED: Array[String]
95
+ CONDITIONAL: Array[String]
96
+
97
+ def self.qualifying: (raw_message: String, signatures: Array[Signature],
98
+ ?organizational_domain: ^(String) -> String?) -> Signature?
91
99
  end
92
100
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mailschema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - MailSchema contributors
@@ -10,61 +10,36 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: bigdecimal
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - ">="
17
- - !ruby/object:Gem::Version
18
- version: '3.1'
19
- - - "<"
20
- - !ruby/object:Gem::Version
21
- version: '5'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- version: '3.1'
29
- - - "<"
30
- - !ruby/object:Gem::Version
31
- version: '5'
32
- - !ruby/object:Gem::Dependency
33
- name: json
13
+ name: json_schemer
34
14
  requirement: !ruby/object:Gem::Requirement
35
15
  requirements:
36
- - - ">="
37
- - !ruby/object:Gem::Version
38
- version: '2.21'
39
- - - "<"
16
+ - - "~>"
40
17
  - !ruby/object:Gem::Version
41
- version: '4'
18
+ version: '2.5'
42
19
  type: :runtime
43
20
  prerelease: false
44
21
  version_requirements: !ruby/object:Gem::Requirement
45
22
  requirements:
46
- - - ">="
47
- - !ruby/object:Gem::Version
48
- version: '2.21'
49
- - - "<"
23
+ - - "~>"
50
24
  - !ruby/object:Gem::Version
51
- version: '4'
25
+ version: '2.5'
52
26
  - !ruby/object:Gem::Dependency
53
- name: json_schemer
27
+ name: mail
54
28
  requirement: !ruby/object:Gem::Requirement
55
29
  requirements:
56
30
  - - "~>"
57
31
  - !ruby/object:Gem::Version
58
- version: '2.5'
32
+ version: '2.8'
59
33
  type: :runtime
60
34
  prerelease: false
61
35
  version_requirements: !ruby/object:Gem::Requirement
62
36
  requirements:
63
37
  - - "~>"
64
38
  - !ruby/object:Gem::Version
65
- version: '2.5'
66
- description: Parse, canonicalize, digest and validate Mail Action Protocol 0.2 documents,
67
- verify the type contracts an implementation vendors, and build results and problems.
39
+ version: '2.8'
40
+ description: Parse, canonicalize, digest and validate Mail Action Protocol 0.3 descriptions,
41
+ type contracts and implementation records, extract and build the MAP part of an
42
+ email, and decide whether a verified DKIM signature qualifies a message.
68
43
  executables: []
69
44
  extensions: []
70
45
  extra_rdoc_files: []
@@ -72,33 +47,33 @@ files:
72
47
  - CHANGELOG.md
73
48
  - LICENSE
74
49
  - README.md
75
- - contexts/map-0.2.jsonld
50
+ - artifacts/context.jsonld
51
+ - artifacts/contract.schema.json
52
+ - artifacts/core.schema.json
53
+ - artifacts/implementation.schema.json
54
+ - artifacts/profile.json
76
55
  - lib/mailschema.rb
77
56
  - lib/mailschema/artifacts.rb
78
- - lib/mailschema/capability.rb
57
+ - lib/mailschema/authentication.rb
79
58
  - lib/mailschema/contract.rb
80
- - lib/mailschema/document.rb
81
- - lib/mailschema/documents.rb
82
- - lib/mailschema/forms.rb
83
- - lib/mailschema/http.rb
59
+ - lib/mailschema/description.rb
60
+ - lib/mailschema/formats.rb
61
+ - lib/mailschema/header.rb
62
+ - lib/mailschema/implementation.rb
84
63
  - lib/mailschema/jcs.rb
85
- - lib/mailschema/limits.rb
64
+ - lib/mailschema/json.rb
86
65
  - lib/mailschema/message.rb
87
- - lib/mailschema/pointer.rb
88
- - lib/mailschema/references.rb
89
- - lib/mailschema/schema_walk.rb
66
+ - lib/mailschema/origin.rb
67
+ - lib/mailschema/patterns.rb
68
+ - lib/mailschema/subschemas.rb
90
69
  - lib/mailschema/version.rb
91
- - schemas/contribution.schema.json
92
- - schemas/forms-0.1.schema.json
93
- - schemas/map-0.2.schema.json
94
- - schemas/type-contract-0.2.schema.json
95
70
  - sig/mailschema.rbs
96
- homepage: https://mailschema.org/tools
71
+ homepage: https://mailschema.org
97
72
  licenses:
98
73
  - MIT
99
74
  metadata:
100
75
  allowed_push_host: https://rubygems.org
101
- homepage_uri: https://mailschema.org/tools
76
+ homepage_uri: https://mailschema.org
102
77
  source_code_uri: https://github.com/mailschema/ruby
103
78
  changelog_uri: https://github.com/mailschema/ruby/blob/main/CHANGELOG.md
104
79
  bug_tracker_uri: https://github.com/mailschema/ruby/issues
@@ -119,5 +94,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
94
  requirements: []
120
95
  rubygems_version: 4.0.20
121
96
  specification_version: 4
122
- summary: Mail Action Protocol 0.2 tooling
97
+ summary: Mail Action Protocol 0.3 processing
123
98
  test_files: []