Class: Woods::Operator::PipelineGuard

Inherits:
Object
  • Object
show all
Defined in:
lib/woods/operator/pipeline_guard.rb

Overview

Rate limiter for pipeline operations using file-based state.

Enforces a cooldown between consecutive runs of the same operation to prevent accidental repeated extraction or embedding.

Examples:

guard = PipelineGuard.new(state_dir: '/tmp', cooldown: 300)
if guard.allow?(:extraction)
  run_extraction
  guard.record!(:extraction)
end

Instance Method Summary collapse

Constructor Details

#initialize(state_dir:, cooldown: 300) ⇒ PipelineGuard

Returns a new instance of PipelineGuard.

Parameters:

  • state_dir (String)

    Directory for persisting state

  • cooldown (Integer) (defaults to: 300)

    Minimum seconds between runs



24
25
26
27
28
# File 'lib/woods/operator/pipeline_guard.rb', line 24

def initialize(state_dir:, cooldown: 300)
  @state_dir = state_dir
  @cooldown = cooldown
  @state_path = File.join(state_dir, 'pipeline_guard.json')
end

Instance Method Details

#allow?(operation) ⇒ Boolean

Check if an operation is allowed (cooldown elapsed).

Parameters:

  • operation (Symbol, String)

    Operation name

Returns:

  • (Boolean)


34
35
36
37
38
39
# File 'lib/woods/operator/pipeline_guard.rb', line 34

def allow?(operation)
  last = last_run(operation)
  return true if last.nil?

  (Time.now - last) >= @cooldown
end

#last_run(operation) ⇒ Time?

Get the last run time for an operation.

Parameters:

  • operation (Symbol, String)

    Operation name

Returns:

  • (Time, nil)


70
71
72
73
74
75
76
77
78
# File 'lib/woods/operator/pipeline_guard.rb', line 70

def last_run(operation)
  state = read_state
  timestamp = state[operation.to_s]
  return nil if timestamp.nil?

  Time.parse(timestamp)
rescue ArgumentError
  nil
end

#record!(operation) ⇒ void

This method returns an undefined value.

Record that an operation has just run.

Parameters:

  • operation (Symbol, String)

    Operation name



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/woods/operator/pipeline_guard.rb', line 45

def record!(operation)
  FileUtils.mkdir_p(@state_dir)
  File.open(@state_path, File::RDWR | File::CREAT) do |f|
    f.flock(File::LOCK_EX)
    content = f.read
    state = if content.empty?
              {}
            else
              begin
                JSON.parse(content)
              rescue StandardError
                {}
              end
            end
    state[operation.to_s] = Time.now.iso8601
    f.rewind
    f.write(JSON.generate(state))
    f.truncate(f.pos)
  end
end