Class: RailVerdict::Handoff
- Inherits:
-
Object
- Object
- RailVerdict::Handoff
- Defined in:
- lib/rail_verdict/handoff.rb
Defined Under Namespace
Classes: BuildError
Constant Summary collapse
- SCHEMA_VERSION =
"1.0"- ID_PATTERN =
/\Asha256:[0-9a-f]{64}\z/- MAX_DOCUMENT_BYTES =
256 * 1024
Instance Attribute Summary collapse
-
#document ⇒ Object
readonly
Returns the value of attribute document.
Class Method Summary collapse
- .build(receipt:, evidence_set:, railverdict_version: RailVerdict::VERSION, evidence_provenance: nil, source_scope: nil) ⇒ Object
- .deep_freeze(value) ⇒ Object
- .id_for(payload) ⇒ Object
- .parse(text) ⇒ Object
- .parse_document(document) ⇒ Object
Instance Attribute Details
#document ⇒ Object (readonly)
Returns the value of attribute document.
23 24 25 |
# File 'lib/rail_verdict/handoff.rb', line 23 def document @document end |
Class Method Details
.build(receipt:, evidence_set:, railverdict_version: RailVerdict::VERSION, evidence_provenance: nil, source_scope: nil) ⇒ Object
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/rail_verdict/handoff.rb', line 25 def self.build(receipt:, evidence_set:, railverdict_version: RailVerdict::VERSION, evidence_provenance: nil, source_scope: nil) raise BuildError.new(:handoff_unavailable, "receipt is required") if receipt.nil? || !receipt.is_a?(Hash) raise BuildError.new(:handoff_unavailable, "evidence_set is required") if evidence_set.nil? || !evidence_set.is_a?(Hash) # Validate receipt structurally (reuse Receipt validation without re-persisting) errors = SchemaValidator.validate_receipt(receipt) raise BuildError.new(:receipt_schema_invalid, errors.join("; ")) unless errors.empty? payload = { "schema_version" => SCHEMA_VERSION, "railverdict_version" => railverdict_version.to_s, "receipt" => receipt, "evidence_set" => normalize_evidence_set(evidence_set) } payload["evidence_provenance"] = normalize_provenance(evidence_provenance) if evidence_provenance payload["source_scope"] = normalize_scope(source_scope) if source_scope document = payload.merge("handoff_id" => id_for(payload)) errors = SchemaValidator.validate_handoff(document) raise BuildError.new(:handoff_schema_violation, errors.join("; ")) unless errors.empty? raise BuildError.new(:handoff_too_large, "handoff exceeds #{MAX_DOCUMENT_BYTES} bytes") if JSON.generate(document).bytesize > MAX_DOCUMENT_BYTES deep_freeze(document) end |
.deep_freeze(value) ⇒ Object
95 96 97 98 99 100 101 102 |
# File 'lib/rail_verdict/handoff.rb', line 95 def self.deep_freeze(value) case value when Hash then value.each { |k, v| k.freeze if k.is_a?(String); deep_freeze(v) }; value.freeze when Array then value.each { |c| deep_freeze(c) }; value.freeze when String then value.freeze else value end end |
.id_for(payload) ⇒ Object
50 51 52 |
# File 'lib/rail_verdict/handoff.rb', line 50 def self.id_for(payload) "sha256:#{Digest::SHA256.hexdigest(CanonicalJSON.generate(payload))}" end |
.parse(text) ⇒ Object
104 105 106 107 108 |
# File 'lib/rail_verdict/handoff.rb', line 104 def self.parse(text) return [nil, :handoff_too_large] if text.bytesize > MAX_DOCUMENT_BYTES document = begin JSON.parse(text) rescue return [nil, :handoff_malformed] end parse_document(document) end |
.parse_document(document) ⇒ Object
110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/rail_verdict/handoff.rb', line 110 def self.parse_document(document) return [nil, :handoff_malformed] unless document.is_a?(Hash) return [nil, :incompatible_handoff_version] unless document["schema_version"] == SCHEMA_VERSION errors = SchemaValidator.validate_handoff(document) return [nil, :handoff_schema_invalid] unless errors.empty? stored = document["handoff_id"] payload = document.reject { |k, _| k == "handoff_id" } expected = id_for(payload) return [nil, :handoff_integrity_failed] unless stored == expected [deep_freeze(document), nil] end |