invoance 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/CHANGELOG.md +29 -0
- data/LICENSE +21 -0
- data/README.md +198 -0
- data/lib/invoance/audit_canonical.rb +144 -0
- data/lib/invoance/audit_verify.rb +121 -0
- data/lib/invoance/client.rb +84 -0
- data/lib/invoance/config.rb +41 -0
- data/lib/invoance/errors.rb +104 -0
- data/lib/invoance/http.rb +174 -0
- data/lib/invoance/resources/attestations.rb +171 -0
- data/lib/invoance/resources/audit.rb +204 -0
- data/lib/invoance/resources/documents.rb +98 -0
- data/lib/invoance/resources/events.rb +71 -0
- data/lib/invoance/resources/traces.rb +60 -0
- data/lib/invoance/validate.rb +32 -0
- data/lib/invoance/version.rb +5 -0
- data/lib/invoance.rb +28 -0
- metadata +108 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
require_relative "../validate"
|
|
6
|
+
|
|
7
|
+
module Invoance
|
|
8
|
+
module Resources
|
|
9
|
+
# Documents resource — client.documents.*
|
|
10
|
+
class Documents
|
|
11
|
+
# @param http [Invoance::Http]
|
|
12
|
+
def initialize(http)
|
|
13
|
+
@http = http
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# POST /document/anchor — Anchor a document hash.
|
|
17
|
+
#
|
|
18
|
+
# @param document_hash [String] hex SHA-256 (validated)
|
|
19
|
+
# @return [Hash] AnchorDocumentResponse
|
|
20
|
+
def anchor(document_hash:, document_ref: nil, event_type: nil, original_bytes_b64: nil,
|
|
21
|
+
metadata: nil, trace_id: nil, idempotency_key: nil)
|
|
22
|
+
Validate.assert_sha256_hex("document_hash", document_hash)
|
|
23
|
+
body = { "document_hash" => document_hash }
|
|
24
|
+
body["document_ref"] = document_ref unless document_ref.nil?
|
|
25
|
+
body["event_type"] = event_type unless event_type.nil?
|
|
26
|
+
body["original_bytes_b64"] = original_bytes_b64 unless original_bytes_b64.nil?
|
|
27
|
+
body["metadata"] = metadata unless metadata.nil?
|
|
28
|
+
body["trace_id"] = trace_id unless trace_id.nil?
|
|
29
|
+
@http.post("/document/anchor", body, idempotency_key)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Convenience helper — reads a file (path String or raw bytes String),
|
|
33
|
+
# computes the SHA-256 hash, base64-encodes the bytes, then anchors.
|
|
34
|
+
#
|
|
35
|
+
# @param file [String] a filesystem path OR raw file bytes
|
|
36
|
+
# @param is_path [Boolean] whether `file` is a path (default: true).
|
|
37
|
+
# When bytes are passed directly, set is_path: false.
|
|
38
|
+
# @param skip_original [Boolean] omit uploading the original bytes
|
|
39
|
+
# @return [Hash] AnchorDocumentResponse
|
|
40
|
+
def anchor_file(file:, is_path: true, document_ref: nil, event_type: nil,
|
|
41
|
+
metadata: nil, trace_id: nil, idempotency_key: nil, skip_original: false)
|
|
42
|
+
content =
|
|
43
|
+
if is_path
|
|
44
|
+
File.binread(file)
|
|
45
|
+
else
|
|
46
|
+
file.dup.force_encoding(Encoding::BINARY)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
document_hash = Digest::SHA256.hexdigest(content)
|
|
50
|
+
ref = document_ref || (is_path ? File.basename(file) : nil)
|
|
51
|
+
# Strict Base64 (no line breaks) via stdlib pack — avoids depending on
|
|
52
|
+
# the `base64` gem, which left Ruby's default gems in 3.4+.
|
|
53
|
+
original_b64 = skip_original ? nil : [content].pack("m0")
|
|
54
|
+
|
|
55
|
+
anchor(
|
|
56
|
+
document_hash: document_hash,
|
|
57
|
+
document_ref: ref,
|
|
58
|
+
event_type: event_type,
|
|
59
|
+
metadata: metadata,
|
|
60
|
+
idempotency_key: idempotency_key,
|
|
61
|
+
original_bytes_b64: original_b64,
|
|
62
|
+
trace_id: trace_id
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# GET /document — Paginated document listing.
|
|
67
|
+
# @return [Hash] ListDocumentsResponse
|
|
68
|
+
def list(page: nil, limit: nil, date_from: nil, date_to: nil, document_ref: nil)
|
|
69
|
+
@http.get("/document", {
|
|
70
|
+
"page" => page,
|
|
71
|
+
"limit" => limit,
|
|
72
|
+
"date_from" => date_from,
|
|
73
|
+
"date_to" => date_to,
|
|
74
|
+
"document_ref" => document_ref
|
|
75
|
+
})
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# GET /document/:event_id — Retrieve a single document.
|
|
79
|
+
# @return [Hash] DocumentEvent
|
|
80
|
+
def get(event_id)
|
|
81
|
+
@http.get("/document/#{event_id}")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# GET /document/:event_id/original — Download the original file.
|
|
85
|
+
# @return [String] raw binary bytes
|
|
86
|
+
def get_original(event_id)
|
|
87
|
+
@http.get_bytes("/document/#{event_id}/original")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# POST /document/:event_id/verify — Hash verification.
|
|
91
|
+
# @return [Hash] VerifyDocumentResponse
|
|
92
|
+
def verify(event_id, document_hash:)
|
|
93
|
+
Validate.assert_sha256_hex("document_hash", document_hash)
|
|
94
|
+
@http.post("/document/#{event_id}/verify", { "document_hash" => document_hash })
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../errors"
|
|
4
|
+
require_relative "../validate"
|
|
5
|
+
|
|
6
|
+
module Invoance
|
|
7
|
+
module Resources
|
|
8
|
+
# Events resource — client.events.*
|
|
9
|
+
class Events
|
|
10
|
+
# @param http [Invoance::Http]
|
|
11
|
+
def initialize(http)
|
|
12
|
+
@http = http
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# POST /events — Ingest a compliance event.
|
|
16
|
+
#
|
|
17
|
+
# @param event_type [String]
|
|
18
|
+
# @param payload [Hash]
|
|
19
|
+
# @param event_time [String, nil]
|
|
20
|
+
# @param trace_id [String, nil]
|
|
21
|
+
# @param idempotency_key [String, nil]
|
|
22
|
+
# @return [Hash] IngestEventResponse
|
|
23
|
+
def ingest(event_type:, payload:, event_time: nil, trace_id: nil, idempotency_key: nil)
|
|
24
|
+
body = { "event_type" => event_type, "payload" => payload }
|
|
25
|
+
body["event_time"] = event_time if event_time
|
|
26
|
+
body["trace_id"] = trace_id if trace_id
|
|
27
|
+
@http.post("/events", body, idempotency_key)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# GET /events — Paginated event listing.
|
|
31
|
+
#
|
|
32
|
+
# @return [Hash] ListEventsResponse
|
|
33
|
+
def list(page: nil, limit: nil, date_from: nil, date_to: nil, event_type: nil)
|
|
34
|
+
@http.get("/events", {
|
|
35
|
+
"page" => page,
|
|
36
|
+
"limit" => limit,
|
|
37
|
+
"date_from" => date_from,
|
|
38
|
+
"date_to" => date_to,
|
|
39
|
+
"event_type" => event_type
|
|
40
|
+
})
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# GET /events/:event_id — Retrieve a single event.
|
|
44
|
+
# @return [Hash] ComplianceEvent
|
|
45
|
+
def get(event_id)
|
|
46
|
+
@http.get("/events/#{event_id}")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# POST /events/:event_id/verify — Hash verification.
|
|
50
|
+
#
|
|
51
|
+
# Provide EITHER payload_hash (hex SHA-256) OR payload (raw JSON).
|
|
52
|
+
# Passing neither raises {Invoance::ValidationError}.
|
|
53
|
+
#
|
|
54
|
+
# @return [Hash] VerifyEventResponse
|
|
55
|
+
def verify(event_id, payload_hash: nil, payload: nil)
|
|
56
|
+
if payload_hash.nil? && payload.nil?
|
|
57
|
+
raise ValidationError, "events.verify requires either `payload_hash` or `payload`"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
body = {}
|
|
61
|
+
unless payload_hash.nil?
|
|
62
|
+
Validate.assert_sha256_hex("payload_hash", payload_hash)
|
|
63
|
+
body["payload_hash"] = payload_hash
|
|
64
|
+
end
|
|
65
|
+
body["payload"] = payload unless payload.nil?
|
|
66
|
+
|
|
67
|
+
@http.post("/events/#{event_id}/verify", body)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Invoance
|
|
4
|
+
module Resources
|
|
5
|
+
# Traces resource — client.traces.*
|
|
6
|
+
class Traces
|
|
7
|
+
# @param http [Invoance::Http]
|
|
8
|
+
def initialize(http)
|
|
9
|
+
@http = http
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# POST /traces — Create a new trace.
|
|
13
|
+
# @return [Hash] CreateTraceResponse
|
|
14
|
+
def create(label:, metadata: nil)
|
|
15
|
+
body = { "label" => label }
|
|
16
|
+
body["metadata"] = metadata if metadata
|
|
17
|
+
@http.post("/traces", body)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# GET /traces — Paginated trace listing. status in open|sealed.
|
|
21
|
+
# @return [Hash] ListTracesResponse
|
|
22
|
+
def list(page: nil, limit: nil, status: nil)
|
|
23
|
+
@http.get("/traces", { "page" => page, "limit" => limit, "status" => status })
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# GET /traces/:trace_id — Trace detail with paginated events.
|
|
27
|
+
# @return [Hash] TraceDetail
|
|
28
|
+
def get(trace_id, event_page: nil, event_limit: nil)
|
|
29
|
+
@http.get("/traces/#{trace_id}", {
|
|
30
|
+
"event_page" => event_page,
|
|
31
|
+
"event_limit" => event_limit
|
|
32
|
+
})
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# DELETE /traces/:trace_id — Delete an empty open trace.
|
|
36
|
+
# @return [Hash] DeleteTraceResponse
|
|
37
|
+
def delete(trace_id)
|
|
38
|
+
@http.delete("/traces/#{trace_id}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# POST /traces/:trace_id/seal — Seal a trace (async, 202).
|
|
42
|
+
# @return [Hash] SealTraceResponse
|
|
43
|
+
def seal(trace_id)
|
|
44
|
+
@http.post("/traces/#{trace_id}/seal", {})
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# GET /traces/:trace_id/proof — Export proof bundle as JSON.
|
|
48
|
+
# @return [Hash] TraceProofBundle
|
|
49
|
+
def proof(trace_id)
|
|
50
|
+
@http.get("/traces/#{trace_id}/proof")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# GET /traces/:trace_id/proof/pdf — Download proof bundle as PDF.
|
|
54
|
+
# @return [String] raw binary PDF bytes
|
|
55
|
+
def proof_pdf(trace_id)
|
|
56
|
+
@http.get_bytes("/traces/#{trace_id}/proof/pdf")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
|
|
5
|
+
module Invoance
|
|
6
|
+
# Shared client-side input validators.
|
|
7
|
+
# @api private
|
|
8
|
+
module Validate
|
|
9
|
+
HEX_SHA256 = /\A[0-9a-f]{64}\z/.freeze
|
|
10
|
+
|
|
11
|
+
# Validate that a value is a 64-char lowercase hex SHA-256 digest.
|
|
12
|
+
# Raises {Invoance::ValidationError} with a helpful message otherwise.
|
|
13
|
+
#
|
|
14
|
+
# @param field_name [String]
|
|
15
|
+
# @param value [Object]
|
|
16
|
+
# @return [void]
|
|
17
|
+
def self.assert_sha256_hex(field_name, value)
|
|
18
|
+
unless value.is_a?(String)
|
|
19
|
+
raise ValidationError,
|
|
20
|
+
"#{field_name} must be a string containing a 64-char hex SHA-256 digest " \
|
|
21
|
+
"(got #{value.class})"
|
|
22
|
+
end
|
|
23
|
+
if value.length != 64
|
|
24
|
+
raise ValidationError, "#{field_name} must be 64 hex chars (got #{value.length} chars)"
|
|
25
|
+
end
|
|
26
|
+
unless HEX_SHA256.match?(value)
|
|
27
|
+
raise ValidationError,
|
|
28
|
+
"#{field_name} must be lowercase hex [0-9a-f]; \"#{value[0, 16]}…\" is not"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/invoance.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Invoance — Official Ruby SDK for the Invoance compliance API.
|
|
4
|
+
#
|
|
5
|
+
# require "invoance"
|
|
6
|
+
#
|
|
7
|
+
# client = Invoance::Client.new(api_key: "inv_live_abc123")
|
|
8
|
+
# event = client.events.ingest(event_type: "user.login", payload: { user_id: "u_42" })
|
|
9
|
+
#
|
|
10
|
+
# Audit-log offline verification helpers live in {Invoance::AuditVerify} and
|
|
11
|
+
# {Invoance::AuditCanonical}; the content-idempotency-key helper is
|
|
12
|
+
# {Invoance::Resources.content_idempotency_key}.
|
|
13
|
+
module Invoance
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
require_relative "invoance/version"
|
|
17
|
+
require_relative "invoance/errors"
|
|
18
|
+
require_relative "invoance/config"
|
|
19
|
+
require_relative "invoance/validate"
|
|
20
|
+
require_relative "invoance/http"
|
|
21
|
+
require_relative "invoance/audit_canonical"
|
|
22
|
+
require_relative "invoance/audit_verify"
|
|
23
|
+
require_relative "invoance/resources/events"
|
|
24
|
+
require_relative "invoance/resources/documents"
|
|
25
|
+
require_relative "invoance/resources/attestations"
|
|
26
|
+
require_relative "invoance/resources/traces"
|
|
27
|
+
require_relative "invoance/resources/audit"
|
|
28
|
+
require_relative "invoance/client"
|
metadata
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: invoance
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Invoance, Inc.
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-07-06 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rake
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '13.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '13.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.12'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.12'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: webmock
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.19'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.19'
|
|
55
|
+
description: 'Official Ruby SDK for the Invoance compliance API. Cryptographic proof
|
|
56
|
+
infrastructure: sign and verify events, documents, AI attestations, traces, and
|
|
57
|
+
audit logs. Zero runtime dependencies.'
|
|
58
|
+
email:
|
|
59
|
+
- sdk@invoance.com
|
|
60
|
+
executables: []
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files: []
|
|
63
|
+
files:
|
|
64
|
+
- CHANGELOG.md
|
|
65
|
+
- LICENSE
|
|
66
|
+
- README.md
|
|
67
|
+
- lib/invoance.rb
|
|
68
|
+
- lib/invoance/audit_canonical.rb
|
|
69
|
+
- lib/invoance/audit_verify.rb
|
|
70
|
+
- lib/invoance/client.rb
|
|
71
|
+
- lib/invoance/config.rb
|
|
72
|
+
- lib/invoance/errors.rb
|
|
73
|
+
- lib/invoance/http.rb
|
|
74
|
+
- lib/invoance/resources/attestations.rb
|
|
75
|
+
- lib/invoance/resources/audit.rb
|
|
76
|
+
- lib/invoance/resources/documents.rb
|
|
77
|
+
- lib/invoance/resources/events.rb
|
|
78
|
+
- lib/invoance/resources/traces.rb
|
|
79
|
+
- lib/invoance/validate.rb
|
|
80
|
+
- lib/invoance/version.rb
|
|
81
|
+
homepage: https://invoance.com
|
|
82
|
+
licenses:
|
|
83
|
+
- MIT
|
|
84
|
+
metadata:
|
|
85
|
+
homepage_uri: https://invoance.com
|
|
86
|
+
source_code_uri: https://github.com/Invoance/invoance-ruby
|
|
87
|
+
changelog_uri: https://github.com/Invoance/invoance-ruby/blob/main/CHANGELOG.md
|
|
88
|
+
rubygems_mfa_required: 'true'
|
|
89
|
+
post_install_message:
|
|
90
|
+
rdoc_options: []
|
|
91
|
+
require_paths:
|
|
92
|
+
- lib
|
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: '3.0'
|
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
requirements: []
|
|
104
|
+
rubygems_version: 3.4.19
|
|
105
|
+
signing_key:
|
|
106
|
+
specification_version: 4
|
|
107
|
+
summary: Official Ruby SDK for the Invoance compliance API
|
|
108
|
+
test_files: []
|