verifyanything 0.1.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 +7 -0
- data/LICENSE +45 -0
- data/README.md +69 -0
- data/lib/openverify/bundle.json +5505 -0
- data/lib/openverify.rb +160 -0
- data/lib/verifyanything.rb +3 -0
- metadata +58 -0
data/lib/openverify.rb
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'json_schemer'
|
|
3
|
+
require 'bigdecimal'
|
|
4
|
+
|
|
5
|
+
# Offline wire SDK. See README for the explicitly supported semantic subset.
|
|
6
|
+
module OpenVerify
|
|
7
|
+
SDK_VERSION = '0.1.0'
|
|
8
|
+
VCP_VERSION = 'draft'
|
|
9
|
+
PROVIDER_CONTRACT_VERSION = '0.1'
|
|
10
|
+
SPEC_REVISION = '5f17f402a270882f244a54dde25eda92a085eb39'
|
|
11
|
+
BUNDLE = JSON.parse(File.read(File.join(__dir__, 'openverify/bundle.json'))).freeze
|
|
12
|
+
class ProtocolError < StandardError
|
|
13
|
+
attr_reader :code, :path
|
|
14
|
+
def initialize(code, message = code, path = '')
|
|
15
|
+
@code, @path = code, path
|
|
16
|
+
super(message)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
# Public JSON object model; absent keys and explicit null remain distinct.
|
|
20
|
+
class WireObject < Hash
|
|
21
|
+
def to_json(*args) = super(*args)
|
|
22
|
+
end
|
|
23
|
+
class StrictHash < Hash
|
|
24
|
+
def []=(key, value)
|
|
25
|
+
raise JSON::ParserError, 'duplicate JSON key' if key?(key)
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
def self.check(schema, value, code)
|
|
30
|
+
resolver = ->(uri) { BUNDLE['schemas'].fetch(uri.to_s) { raise ProtocolError.new('INVALID_PROVIDER_OUTPUT', 'Offline unknown reference') } }
|
|
31
|
+
validator = JSONSchemer.schema(numbers(schema), meta_schema: JSONSchemer.draft202012, format: false, ref_resolver: resolver)
|
|
32
|
+
raise ProtocolError.new(code) unless validator.valid?(numbers(value))
|
|
33
|
+
rescue JSONSchemer::InvalidRefResolution, JSONSchemer::UnknownRef => e
|
|
34
|
+
raise ProtocolError.new('INVALID_PROVIDER_OUTPUT', e.message)
|
|
35
|
+
end
|
|
36
|
+
# JSON number equality must not inherit Ruby's Integer/Float hash distinction.
|
|
37
|
+
def self.numbers(value)
|
|
38
|
+
case value
|
|
39
|
+
when Numeric then BigDecimal(value.to_s)
|
|
40
|
+
when Hash then value.transform_values { |v| numbers(v) }
|
|
41
|
+
when Array then value.map { |v| numbers(v) }
|
|
42
|
+
else value
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
def self.validate_structure(contract, kind, value)
|
|
46
|
+
prefix = {'vcp'=>'urn:verification:draft:', 'provider'=>'urn:openverify:provider:0.1:'}[contract]
|
|
47
|
+
raise ProtocolError.new('SDK_UNSUPPORTED') unless prefix && (schema = BUNDLE['schemas'][prefix + kind])
|
|
48
|
+
if contract == 'vcp' && value.is_a?(Hash) && value.key?('protocol') && value['protocol'] != 'draft'
|
|
49
|
+
raise ProtocolError.new('PROTOCOL_VERSION')
|
|
50
|
+
end
|
|
51
|
+
if contract == 'provider' && kind == 'invocation' && value.is_a?(Hash) && value.key?('contract_version') && value['contract_version'] != '0.1'
|
|
52
|
+
raise ProtocolError.new('CONTRACT_VERSION_UNSUPPORTED')
|
|
53
|
+
end
|
|
54
|
+
check(schema, value, contract == 'vcp' ? 'PROTOCOL_STRUCTURE' : kind == 'invocation' ? 'INVALID_INVOCATION' : 'INVALID_PROVIDER_OUTPUT')
|
|
55
|
+
end
|
|
56
|
+
def self.validate_data(schema, value, code = 'INVALID_INPUT')
|
|
57
|
+
positions, refs = {}, []
|
|
58
|
+
visit = nil
|
|
59
|
+
visit = lambda do |s, path, top|
|
|
60
|
+
positions[path] = true
|
|
61
|
+
next if s == true || s == false
|
|
62
|
+
raise ProtocolError.new('INVALID_PROVIDER_OUTPUT') unless s.is_a?(Hash)
|
|
63
|
+
if (!top && s.key?('$id')) || %w[$anchor $dynamicAnchor $dynamicRef].any? { |k| s.key?(k) }
|
|
64
|
+
raise ProtocolError.new('INVALID_PROVIDER_OUTPUT')
|
|
65
|
+
end
|
|
66
|
+
raise ProtocolError.new('INVALID_PROVIDER_OUTPUT') if s.key?('$schema') && s['$schema'] != 'https://json-schema.org/draft/2020-12/schema'
|
|
67
|
+
if s.key?('$ref')
|
|
68
|
+
ref = s['$ref']
|
|
69
|
+
raise ProtocolError.new('INVALID_PROVIDER_OUTPUT') unless ref.is_a?(String) && (ref == '#' || ref.start_with?('#/'))
|
|
70
|
+
refs << ref[1..]
|
|
71
|
+
end
|
|
72
|
+
%w[properties patternProperties $defs dependentSchemas].each do |k|
|
|
73
|
+
s.fetch(k, {}).each { |name, v| visit.call(v, "#{path}/#{k}/#{name.gsub('~','~0').gsub('/','~1')}", false) } if s.fetch(k, {}).is_a?(Hash)
|
|
74
|
+
end
|
|
75
|
+
%w[additionalProperties unevaluatedProperties propertyNames contains items unevaluatedItems not if then else contentSchema].each { |k| visit.call(s[k], "#{path}/#{k}", false) if s.key?(k) }
|
|
76
|
+
%w[allOf anyOf oneOf prefixItems].each { |k| s.fetch(k, []).each_with_index { |v, n| visit.call(v, "#{path}/#{k}/#{n}", false) } if s.fetch(k, []).is_a?(Array) }
|
|
77
|
+
end
|
|
78
|
+
visit.call(schema, '', true)
|
|
79
|
+
raise ProtocolError.new('INVALID_PROVIDER_OUTPUT') unless refs.all? { |r| positions[r] }
|
|
80
|
+
validator = JSONSchemer.schema(numbers(schema), meta_schema: JSONSchemer.draft202012, format: false, ref_resolver: ->(_) { raise ProtocolError.new('INVALID_PROVIDER_OUTPUT') })
|
|
81
|
+
raise ProtocolError.new('INVALID_PROVIDER_OUTPUT') unless validator.valid_schema?
|
|
82
|
+
raise ProtocolError.new(code) unless validator.valid?(numbers(value))
|
|
83
|
+
end
|
|
84
|
+
def self.validate(contract, kind, value)
|
|
85
|
+
validate_structure(contract, kind, value)
|
|
86
|
+
raise ProtocolError.new('SDK_UNSUPPORTED') if contract == 'vcp' && !BUNDLE['semantic_vcp_types'].include?(kind)
|
|
87
|
+
BUNDLE['rules'].fetch(kind, []).each { |r| check(r['schema'], value, contract == 'provider' ? 'INVALID_PROVIDER_OUTPUT' : r['code']) }
|
|
88
|
+
if kind == 'resource' && value['kind'] == 'RANGE' && value['minimum'] > value['maximum']
|
|
89
|
+
raise ProtocolError.new('RESOURCE_MEASUREMENT')
|
|
90
|
+
end
|
|
91
|
+
if contract == 'vcp'
|
|
92
|
+
{'extensions'=>'extension', 'evidence'=>'evidence'}.each { |k, target| value.fetch(k, []).each { |v| validate('vcp', target, v) } }
|
|
93
|
+
validate('vcp', 'artifact-ref', value['artifact_ref']) if value['artifact_ref']
|
|
94
|
+
value.fetch('usage', {}).each_value { |v| validate('vcp', 'resource', v) }
|
|
95
|
+
else
|
|
96
|
+
raise ProtocolError.new('SDK_UNSUPPORTED') if value.key?('deadline') || value.key?('timestamp')
|
|
97
|
+
if kind == 'provider-event'
|
|
98
|
+
begin
|
|
99
|
+
target = {'evidence'=>'evidence','artifact'=>'artifact-ref'}[value['kind']]
|
|
100
|
+
validate('vcp',target,value['payload']) if target
|
|
101
|
+
value['payload'].each_value { |r| validate('vcp','resource',r) } if value['kind'] == 'resource_usage'
|
|
102
|
+
rescue ProtocolError
|
|
103
|
+
raise ProtocolError.new('INVALID_PROVIDER_OUTPUT')
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
if kind == 'provider-manifest'
|
|
107
|
+
seen = {}
|
|
108
|
+
value['capabilities'].each do |c|
|
|
109
|
+
key = [c['capability_id'], c['capability_version']]
|
|
110
|
+
raise ProtocolError.new('INVALID_PROVIDER_OUTPUT') if seen[key]
|
|
111
|
+
seen[key] = true
|
|
112
|
+
raise ProtocolError.new('INVALID_PROVIDER_OUTPUT') if c.key?('vcp_version') && !value['supported_vcp_versions'].include?(c['vcp_version'])
|
|
113
|
+
validate('provider', 'capability', c)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
if kind == 'capability'
|
|
117
|
+
%w[input_schema config_schema output_schema].each do |k|
|
|
118
|
+
begin
|
|
119
|
+
validate_data(value[k], nil)
|
|
120
|
+
rescue ProtocolError => e
|
|
121
|
+
raise unless e.code == 'INVALID_INPUT'
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
validate('provider', 'provider-manifest', value['manifest']) if kind == 'initialize-result'
|
|
126
|
+
raise ProtocolError.new(kind == 'invocation' ? 'INVALID_INVOCATION' : 'INVALID_PROVIDER_OUTPUT') if value.key?('vcp_version') && value['vcp_version'] != 'draft'
|
|
127
|
+
%w[resource_budget resource_usage].each do |k|
|
|
128
|
+
value.fetch(k, {}).each_value do |r|
|
|
129
|
+
begin
|
|
130
|
+
validate('vcp', 'resource', r)
|
|
131
|
+
rescue ProtocolError
|
|
132
|
+
raise ProtocolError.new(kind == 'invocation' ? 'INVALID_INVOCATION' : 'INVALID_PROVIDER_OUTPUT')
|
|
133
|
+
end
|
|
134
|
+
raise ProtocolError.new('INVALID_INVOCATION') if k == 'resource_budget' && (r['kind'] != 'EXACT' || value['vcp_version'] != 'draft')
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
WireObject[value]
|
|
139
|
+
end
|
|
140
|
+
def self.parse(contract, kind, text)
|
|
141
|
+
begin
|
|
142
|
+
value = JSON.parse(text, object_class: StrictHash, allow_nan: false, allow_duplicate_key: false)
|
|
143
|
+
precise = JSON.parse(text, decimal_class: BigDecimal, allow_nan: false, allow_duplicate_key: false)
|
|
144
|
+
faithful = lambda do |native, exact|
|
|
145
|
+
if native.is_a?(Float)
|
|
146
|
+
raise JSON::ParserError, 'JSON number cannot be represented faithfully' unless native.finite? && BigDecimal(native.to_s) == exact
|
|
147
|
+
elsif native.is_a?(Hash)
|
|
148
|
+
native.each { |k, v| faithful.call(v, exact[k]) }
|
|
149
|
+
elsif native.is_a?(Array)
|
|
150
|
+
native.each_with_index { |v, n| faithful.call(v, exact[n]) }
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
faithful.call(value, precise)
|
|
154
|
+
rescue JSON::ParserError, EncodingError => e
|
|
155
|
+
raise ProtocolError.new(contract == 'vcp' ? 'PROTOCOL_JSON' : kind == 'invocation' ? 'INVALID_INVOCATION' : 'INVALID_PROVIDER_OUTPUT', e.message)
|
|
156
|
+
end
|
|
157
|
+
validate(contract, kind, value)
|
|
158
|
+
end
|
|
159
|
+
def self.serialize(value) = JSON.generate(value, allow_nan: false)
|
|
160
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: verifyanything
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- OpenVerify contributors
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-10-17 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: json_schemer
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.5'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.5'
|
|
26
|
+
executables: []
|
|
27
|
+
extensions: []
|
|
28
|
+
extra_rdoc_files: []
|
|
29
|
+
files:
|
|
30
|
+
- LICENSE
|
|
31
|
+
- README.md
|
|
32
|
+
- lib/openverify.rb
|
|
33
|
+
- lib/openverify/bundle.json
|
|
34
|
+
- lib/verifyanything.rb
|
|
35
|
+
homepage: https://github.com/openverifyproject/openverify
|
|
36
|
+
licenses:
|
|
37
|
+
- MIT
|
|
38
|
+
metadata:
|
|
39
|
+
source_code_uri: https://github.com/openverifyproject/openverify
|
|
40
|
+
rubygems_mfa_required: 'true'
|
|
41
|
+
rdoc_options: []
|
|
42
|
+
require_paths:
|
|
43
|
+
- lib
|
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '3.2'
|
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
requirements: []
|
|
55
|
+
rubygems_version: 3.6.9
|
|
56
|
+
specification_version: 4
|
|
57
|
+
summary: VerifyAnything DRAFT VCP and Provider Contract wire SDK
|
|
58
|
+
test_files: []
|