Class: Invoance::Resources::Attestations
- Inherits:
-
Object
- Object
- Invoance::Resources::Attestations
- Defined in:
- lib/invoance/resources/attestations.rb
Overview
AI Attestations resource — client.attestations.*
Instance Method Summary collapse
-
#get(attestation_id) ⇒ Hash
GET /ai/attestations/:id — Retrieve a single attestation.
-
#get_raw(attestation_id) ⇒ Hash
GET /ai/attestations/:id/raw — Retrieve the canonical JSON payload.
-
#ingest(type:, input:, output:, model_provider: nil, model_name: nil, model_version: nil, subject: nil, trace_id: nil, idempotency_key: nil) ⇒ Hash
POST /ai/attestations — Anchor an AI attestation.
-
#initialize(http) ⇒ Attestations
constructor
A new instance of Attestations.
-
#list(page: nil, limit: nil, date_from: nil, date_to: nil, attestation_type: nil, model_provider: nil) ⇒ Hash
GET /ai/attestations — Paginated attestation listing.
-
#verify(attestation_id, content_hash:) ⇒ Hash
POST /ai/attestations/:id/verify — Hash verification.
-
#verify_payload(attestation_id, payload) ⇒ Hash
Verify by raw payload — hashes client-side, then calls verify.
-
#verify_signature(attestation_id) ⇒ Hash
Verify the Ed25519 signature of an attestation — fully client-side.
Constructor Details
#initialize(http) ⇒ Attestations
Returns a new instance of Attestations.
14 15 16 |
# File 'lib/invoance/resources/attestations.rb', line 14 def initialize(http) @http = http end |
Instance Method Details
#get(attestation_id) ⇒ Hash
GET /ai/attestations/:id — Retrieve a single attestation.
78 79 80 |
# File 'lib/invoance/resources/attestations.rb', line 78 def get(attestation_id) @http.get("/ai/attestations/#{attestation_id}") end |
#get_raw(attestation_id) ⇒ Hash
GET /ai/attestations/:id/raw — Retrieve the canonical JSON payload.
92 93 94 |
# File 'lib/invoance/resources/attestations.rb', line 92 def get_raw(attestation_id) @http.get_raw("/ai/attestations/#{attestation_id}/raw") end |
#ingest(type:, input:, output:, model_provider: nil, model_name: nil, model_version: nil, subject: nil, trace_id: nil, idempotency_key: nil) ⇒ Hash
POST /ai/attestations — Anchor an AI attestation.
The body is NESTED and field order matters for the server's hashing (type, payload, context, subject) — do not reorder.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/invoance/resources/attestations.rb', line 33 def ingest(type:, input:, output:, model_provider: nil, model_name: nil, model_version: nil, subject: nil, trace_id: nil, idempotency_key: nil) body = { "type" => type, "payload" => { "input" => input, "output" => output }, "context" => { "model_provider" => model_provider, "model_name" => model_name, "model_version" => model_version } } unless subject.nil? s = symbolize_subject(subject) user_id = s.delete(:user_id) session_id = s.delete(:session_id) subj = {} subj["user_id"] = user_id unless user_id.nil? subj["session_id"] = session_id unless session_id.nil? # Pass through any remaining extra keys, preserving their string form. s.each { |k, v| subj[k.to_s] = v } body["subject"] = subj unless subj.empty? end body["trace_id"] = trace_id unless trace_id.nil? @http.post("/ai/attestations", body, idempotency_key) end |
#list(page: nil, limit: nil, date_from: nil, date_to: nil, attestation_type: nil, model_provider: nil) ⇒ Hash
GET /ai/attestations — Paginated attestation listing.
64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/invoance/resources/attestations.rb', line 64 def list(page: nil, limit: nil, date_from: nil, date_to: nil, attestation_type: nil, model_provider: nil) @http.get("/ai/attestations", { "page" => page, "limit" => limit, "date_from" => date_from, "date_to" => date_to, "attestation_type" => attestation_type, "model_provider" => model_provider }) end |
#verify(attestation_id, content_hash:) ⇒ Hash
POST /ai/attestations/:id/verify — Hash verification.
84 85 86 87 88 |
# File 'lib/invoance/resources/attestations.rb', line 84 def verify(attestation_id, content_hash:) Validate.assert_sha256_hex("content_hash", content_hash) @http.post("/ai/attestations/#{attestation_id}/verify", { "content_hash" => content_hash }) end |
#verify_payload(attestation_id, payload) ⇒ Hash
Verify by raw payload — hashes client-side, then calls verify.
Accepts the canonical JSON stored in Invoance as a raw String or a Hash. Key ORDER is PRESERVED (not sorted) because the backend hashes using serde_json struct field order (type, payload, context, subject).
- String input: JSON.parse preserves object key order into an insertion-ordered Hash, and JSON.generate preserves it — giving compact order-preserving JSON (= JS JSON.stringify(JSON.parse(s))).
- Hash input: JSON.generate preserves insertion order — pass the keys already in the correct order.
111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/invoance/resources/attestations.rb', line 111 def verify_payload(attestation_id, payload) canonical = if payload.is_a?(String) JSON.generate(JSON.parse(payload)) else JSON.generate(payload) end content_hash = Digest::SHA256.hexdigest(canonical) verify(attestation_id, content_hash: content_hash) end |
#verify_signature(attestation_id) ⇒ Hash
Verify the Ed25519 signature of an attestation — fully client-side.
Fetches the attestation, then verifies the signature against signed_payload using public_key. Requires no trust in the server.
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/invoance/resources/attestations.rb', line 130 def verify_signature(attestation_id) att = get(attestation_id) signed_payload_bytes = hex_to_bytes(att["signed_payload"].to_s) signature_bytes = hex_to_bytes(att["signature"].to_s) public_key_bytes = hex_to_bytes(att["public_key"].to_s) valid = false reason = nil begin valid = AuditVerify.ed25519_verify(signed_payload_bytes, signature_bytes, public_key_bytes) reason = "Signature does not match signed_payload + public_key" unless valid rescue StandardError => e valid = false reason = e. end signed_data = begin JSON.parse(signed_payload_bytes.dup.force_encoding(Encoding::UTF_8)) rescue JSON::ParserError nil end { "valid" => valid, "reason" => reason, "attestation" => att, "signed_data" => signed_data } end |