Class: Clickwrap::Integrity::Chain

Inherits:
Object
  • Object
show all
Defined in:
lib/clickwrap/integrity/chain.rb

Overview

Walks the optional event chains and reports what still links up.

result = Clickwrap::Integrity::Chain.verify
result.success?     # => true
result.counts       # => {"checked" => 41_882, "verified" => 41_882, "breaks" => 0}
result.first_break  # => nil

============================================================================ WHAT A CHAIN DETECTS, EXACTLY. Each event carries the digest of the one before it, so an event that is later rewritten or removed stops linking up with its successors, and this walk finds the first place that happens. That is a real and useful property: ordinary corruption, a well-meant update_column, a restored partial backup, and a row edited by hand all show up here.

What it does NOT do is stop, or detect, a privileged actor who rewrites an event AND every digest that follows it. Whoever can write the events table can usually write this table too, and a chain whose head lives in the same database as the chain cannot say otherwise. That is precisely the gap the optional independent anchor adapter addresses, and even then the claim is only ever as strong as the anchor. The chain makes rewriting history detectable for as long as the head remains trustworthy — no more than that, and this class never says more than that.

Defined Under Namespace

Classes: Break, Result

Constant Summary collapse

BATCH_SIZE =
1_000
REASONS =

Every way a walk can stop lining up, as a stable symbol so a monitor can branch on it without matching English.

%i[
  digest_does_not_match
  previous_digest_does_not_link
  sequence_gap
  earlier_events_missing
  chain_head_missing
  chain_tail_missing
  chain_head_event_mismatch
  chain_head_digest_mismatch
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(scope: nil, from: nil, to: nil) ⇒ Chain

Returns a new instance of Chain.



102
103
104
105
106
107
108
109
110
111
# File 'lib/clickwrap/integrity/chain.rb', line 102

def initialize(scope: nil, from: nil, to: nil)
  @scope = scope&.to_s
  @from = from
  @to = to
  @breaks = []
  @checked = 0
  @verified = 0
  @documented_dispositions = 0
  @started_mid_chain = []
end

Instance Attribute Details

#fromObject (readonly)

Returns the value of attribute from.



113
114
115
# File 'lib/clickwrap/integrity/chain.rb', line 113

def from
  @from
end

#scopeObject (readonly)

Returns the value of attribute scope.



113
114
115
# File 'lib/clickwrap/integrity/chain.rb', line 113

def scope
  @scope
end

#toObject (readonly)

Returns the value of attribute to.



113
114
115
# File 'lib/clickwrap/integrity/chain.rb', line 113

def to
  @to
end

Class Method Details

.verify(scope: nil, from: nil, to: nil) ⇒ Object

from: and to: accept either a chain sequence number or a time. A sequence is the natural way to re-check one span of a chain; a time is the natural way to run "everything since last night" from cron, and refusing one of them would just make an operator convert by hand.



100
# File 'lib/clickwrap/integrity/chain.rb', line 100

def self.verify(scope: nil, from: nil, to: nil) = new(scope: scope, from: from, to: to).verify

Instance Method Details

#verifyObject



115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/clickwrap/integrity/chain.rb', line 115

def verify
  scopes = chain_scopes
  scopes.each { |chain_scope| walk(chain_scope) }

  Result.new(
    chaining_enabled: !Clickwrap.config.chain_event_history_with.nil?,
    checked: @checked,
    verified: @verified,
    documented_dispositions: @documented_dispositions,
    scopes: scopes,
    breaks: @breaks,
    started_mid_chain: @started_mid_chain
  )
end