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.
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Invoance
4
+ VERSION = "0.1.0"
5
+ 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: []