geneva_drive 0.7.0 → 0.9.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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/MANUAL.md +299 -5
  4. data/README.md +14 -1
  5. data/Rakefile +28 -1
  6. data/bin/matrix +76 -0
  7. data/bin/md2html +114 -0
  8. data/lib/generators/geneva_drive/install/templates/add_resumable_step_support.rb +5 -9
  9. data/lib/generators/geneva_drive/install/templates/add_signals_support.rb +5 -10
  10. data/lib/generators/geneva_drive/install/templates/create_workflows_migration.rb +1 -1
  11. data/lib/generators/geneva_drive/install/templates/initializer.rb.tt +33 -2
  12. data/lib/geneva_drive/dispatchers/active_job_dispatcher.rb +33 -0
  13. data/lib/geneva_drive/dispatchers/sidekiq_dispatcher.rb +112 -0
  14. data/lib/geneva_drive/engine.rb +13 -0
  15. data/lib/geneva_drive/executor.rb +23 -3
  16. data/lib/geneva_drive/job_options.rb +7 -3
  17. data/lib/geneva_drive/jobs/perform_step_job.rb +2 -35
  18. data/lib/geneva_drive/jobs/sidekiq_housekeeping_job.rb +32 -0
  19. data/lib/geneva_drive/jobs/sidekiq_perform_step_job.rb +35 -0
  20. data/lib/geneva_drive/jobs/step_execution_lookup.rb +42 -0
  21. data/lib/geneva_drive/json_column.rb +72 -0
  22. data/lib/geneva_drive/migration_helpers.rb +60 -3
  23. data/lib/geneva_drive/removed_step_definition.rb +34 -0
  24. data/lib/geneva_drive/signal.rb +4 -2
  25. data/lib/geneva_drive/step_collection.rb +31 -7
  26. data/lib/geneva_drive/step_definition.rb +9 -0
  27. data/lib/geneva_drive/step_execution.rb +3 -2
  28. data/lib/geneva_drive/version.rb +1 -1
  29. data/lib/geneva_drive/workflow.rb +200 -39
  30. data/lib/geneva_drive.rb +65 -0
  31. data/test/dummy/config/database.yml +40 -5
  32. data/test/dummy/config/initializers/geneva_drive.rb +78 -0
  33. data/test/dummy/db/mysql/schema-8.1.rb +95 -0
  34. data/test/dummy/db/postgres/schema-8.1.rb +99 -0
  35. data/test/dummy/db/sqlite/schema-7.2.rb +96 -0
  36. data/test/dummy/db/sqlite/schema-8.0.rb +96 -0
  37. data/test/dummy/db/sqlite/schema-8.1.rb +96 -0
  38. data/test/dummy/db/sqlite/test.sqlite3 +0 -0
  39. data/test/dummy/db/sqlite/test.sqlite3-0 +0 -0
  40. data/test/dummy/db/sqlite/test.sqlite3-1 +0 -0
  41. data/test/dummy/db/sqlite/test.sqlite3-2 +0 -0
  42. data/test/dummy/db/sqlite/test.sqlite3-3 +0 -0
  43. data/test/dummy/db/sqlite/test.sqlite3-4 +0 -0
  44. data/test/dummy/db/sqlite/test.sqlite3-5 +0 -0
  45. data/test/dummy/db/sqlite/test.sqlite3-6 +0 -0
  46. data/test/dummy/db/sqlite/test.sqlite3-7 +0 -0
  47. data/test/dummy/db/sqlite/test.sqlite3-8 +0 -0
  48. data/test/dummy/db/sqlite/test.sqlite3-9 +0 -0
  49. data/test/dummy/db/sqlite/test.sqlite3_0 +0 -0
  50. data/test/dummy/db/sqlite/test.sqlite3_1 +0 -0
  51. data/test/dummy/db/sqlite/test.sqlite3_2 +0 -0
  52. data/test/dummy/db/sqlite/test.sqlite3_3 +0 -0
  53. data/test/dummy/db/sqlite/test.sqlite3_4 +0 -0
  54. data/test/dummy/db/sqlite/test.sqlite3_5 +0 -0
  55. data/test/dummy/db/sqlite/test.sqlite3_6 +0 -0
  56. data/test/dummy/db/sqlite/test.sqlite3_7 +0 -0
  57. data/test/dummy/db/sqlite/test.sqlite3_8 +0 -0
  58. data/test/dummy/db/sqlite/test.sqlite3_9 +0 -0
  59. data/test/dummy/db/sqlite3/schema-7.2.rb +96 -0
  60. data/test/dummy/db/sqlite3/schema-8.1.rb +96 -0
  61. data/test/dummy/db/sqlite3/test.sqlite3 +0 -0
  62. data/test/dummy/db/sqlite3/test.sqlite3_0 +0 -0
  63. data/test/dummy/db/sqlite3/test.sqlite3_1 +0 -0
  64. data/test/dummy/db/sqlite3/test.sqlite3_2 +0 -0
  65. data/test/dummy/db/sqlite3/test.sqlite3_3 +0 -0
  66. data/test/dummy/db/sqlite3/test.sqlite3_4 +0 -0
  67. data/test/dummy/db/sqlite3/test.sqlite3_5 +0 -0
  68. data/test/dummy/db/sqlite3/test.sqlite3_6 +0 -0
  69. data/test/dummy/db/sqlite3/test.sqlite3_7 +0 -0
  70. data/test/dummy/db/sqlite3/test.sqlite3_8 +0 -0
  71. data/test/dummy/db/sqlite3/test.sqlite3_9 +0 -0
  72. data/test/dummy_install/db/migrate/20260217112949_create_geneva_drive_workflows.rb +76 -0
  73. data/test/dummy_install/db/migrate/20260217112950_create_geneva_drive_step_executions.rb +99 -0
  74. data/test/dummy_install/db/migrate/20260217112951_add_finished_at_to_geneva_drive_step_executions.rb +27 -0
  75. data/test/dummy_install/db/migrate/20260217112952_add_error_class_name_to_geneva_drive_step_executions.rb +9 -0
  76. data/test/dummy_install/db/migrate/20260217112953_add_resumable_step_support_to_geneva_drive_step_executions.rb +128 -0
  77. data/test/dummy_install/db/migrate/20260217112954_add_chained_step_executions_to_geneva_drive_step_executions.rb +136 -0
  78. data/test/dummy_install/db/schema.rb +75 -0
  79. data/test/dummy_install/log/test.log +1852 -0
  80. data/test/generators/install_generator_test.rb +56 -0
  81. data/test/geneva_drive_test.rb +10 -0
  82. data/test/migration_helpers_test.rb +62 -0
  83. data/test/sidekiq/sidekiq_dispatcher_test.rb +322 -0
  84. data/test/sidekiq/sidekiq_redis_test.rb +115 -0
  85. data/test/test_helper.rb +51 -13
  86. data/test/workflow/pause_resume_test.rb +15 -0
  87. data/test/workflow/removed_step_test.rb +294 -0
  88. data/test/workflow/resume_at_test.rb +170 -0
  89. metadata +62 -1
data/lib/geneva_drive.rb CHANGED
@@ -39,6 +39,7 @@ module GenevaDrive
39
39
  autoload :FlowControl, "geneva_drive/flow_control"
40
40
  autoload :StepDefinition, "geneva_drive/step_definition"
41
41
  autoload :ResumableStepDefinition, "geneva_drive/resumable_step_definition"
42
+ autoload :RemovedStepDefinition, "geneva_drive/removed_step_definition"
42
43
  autoload :StepCollection, "geneva_drive/step_collection"
43
44
  autoload :Workflow, "geneva_drive/workflow"
44
45
  autoload :StepExecution, "geneva_drive/step_execution"
@@ -49,12 +50,20 @@ module GenevaDrive
49
50
  autoload :ExceptionPolicy, "geneva_drive/exception_policy"
50
51
  autoload :JobOptions, "geneva_drive/job_options"
51
52
  autoload :CombinedExceptionPolicy, "geneva_drive/combined_exception_policy"
53
+ autoload :JsonColumn, "geneva_drive/json_column"
52
54
  autoload :MigrationHelpers, "geneva_drive/migration_helpers"
53
55
  autoload :TestHelpers, "geneva_drive/test_helpers"
54
56
 
57
+ # Dispatchers
58
+ autoload :ActiveJobDispatcher, "geneva_drive/dispatchers/active_job_dispatcher"
59
+ autoload :SidekiqDispatcher, "geneva_drive/dispatchers/sidekiq_dispatcher"
60
+
55
61
  # Jobs
62
+ autoload :StepExecutionLookup, "geneva_drive/jobs/step_execution_lookup"
56
63
  autoload :PerformStepJob, "geneva_drive/jobs/perform_step_job"
57
64
  autoload :HousekeepingJob, "geneva_drive/jobs/housekeeping_job"
65
+ autoload :SidekiqPerformStepJob, "geneva_drive/jobs/sidekiq_perform_step_job"
66
+ autoload :SidekiqHousekeepingJob, "geneva_drive/jobs/sidekiq_housekeeping_job"
58
67
 
59
68
  class << self
60
69
  # How long to keep completed workflows before cleanup.
@@ -178,6 +187,62 @@ module GenevaDrive
178
187
  Thread.current[:geneva_drive_inline_enqueue] = previous
179
188
  end
180
189
 
190
+ # Turns "this step execution should run" into a job: `:active_job` (the
191
+ # default, see ActiveJobDispatcher) or `:sidekiq` (see SidekiqDispatcher).
192
+ # Set it in an initializer, before any workflow class loads, since step
193
+ # job options are validated against it when a workflow class is defined.
194
+ #
195
+ # @example Run step jobs as native Sidekiq jobs
196
+ # GenevaDrive.dispatcher = :sidekiq
197
+ #
198
+ # @return [#enqueue_step, #validate_job_options!]
199
+ def dispatcher
200
+ @dispatcher ||= GenevaDrive::ActiveJobDispatcher.new
201
+ end
202
+
203
+ # @param value [Symbol, #enqueue_step] `:active_job`, `:sidekiq`, or a
204
+ # dispatcher object
205
+ # @return [void]
206
+ # @raise [ArgumentError] for anything else
207
+ def dispatcher=(value)
208
+ @dispatcher = case value
209
+ when :active_job
210
+ GenevaDrive::ActiveJobDispatcher.new
211
+ when :sidekiq
212
+ GenevaDrive::SidekiqDispatcher.new
213
+ else
214
+ unless value.respond_to?(:enqueue_step) && value.respond_to?(:validate_job_options!)
215
+ raise ArgumentError,
216
+ "GenevaDrive.dispatcher must be :active_job, :sidekiq, or an object responding to " \
217
+ "enqueue_step and validate_job_options! (got #{value.inspect})"
218
+ end
219
+ value
220
+ end
221
+ end
222
+
223
+ # Tells resumable steps running in this process to hand off at their next
224
+ # iteration boundary. Wired to Sidekiq's :quiet event by the engine, which
225
+ # fires on TSTP and at the start of every shutdown. Process-wide, unlike
226
+ # the thread-local flag the executor also honours.
227
+ #
228
+ # @return [void]
229
+ # @api private
230
+ def request_exit!
231
+ @exit_requested = true
232
+ end
233
+
234
+ # @return [Boolean] whether the host process asked running work to stop
235
+ # @api private
236
+ def exit_requested?
237
+ @exit_requested == true
238
+ end
239
+
240
+ # @return [void]
241
+ # @api private
242
+ def reset_exit_request!
243
+ @exit_requested = false
244
+ end
245
+
181
246
  # Returns true if job enqueueing should be deferred to after transaction commit.
182
247
  #
183
248
  # @return [Boolean]
@@ -1,13 +1,48 @@
1
- # PostgreSQL configuration for GenevaDrive test suite
1
+ <%
2
+ # Everything the database tasks write under db/ is adapter-specific, the
3
+ # schema dump above all: parallelize() builds every worker database out of
4
+ # that dump, and a PostgreSQL one carries jsonb columns SQLite cannot load.
5
+ # CI points DATABASE_URL at a different engine per leg, so each engine gets
6
+ # its own corner of db/ and the legs stop overwriting each other's artifacts.
7
+ #
8
+ # This has to be resolved here rather than in an initializer - db:prepare and
9
+ # the other database tasks hang off :load_config, which never runs those.
10
+ url = ENV["DATABASE_URL"]
11
+ adapter = if url
12
+ ActiveRecord::DatabaseConfigurations::ConnectionUrlResolver.new(url).to_hash[:adapter]
13
+ else
14
+ "sqlite3"
15
+ end
16
+
17
+ # MariaDB and MySQL are both the mysql2 adapter but do not produce the same
18
+ # dump - MariaDB has no JSON type - so the engine, when the runner names one,
19
+ # is what the artifacts get filed under.
20
+ engine = ENV.fetch("GENEVA_DRIVE_ENGINE", adapter)
21
+ artifacts = "db/#{engine}"
22
+
23
+ # The dump is keyed by Rails minor on top of that: it is written as
24
+ # `ActiveRecord::Schema[8.1].define`, and 7.2 refuses to load a version it
25
+ # has never heard of. The databases themselves are not version-specific -
26
+ # the migrations produce the same DDL - so only the dump gets the suffix.
27
+ schema_dump = "#{engine}/schema-#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}.rb"
28
+
29
+ # Rails only creates db/ itself, not the subdirectory the dump lives in.
30
+ require "fileutils"
31
+ FileUtils.mkdir_p(Rails.root.join(artifacts))
32
+ %>
33
+ # The test suite runs against SQLite by default so that a fresh clone can run
34
+ # `rake` with no database server. CI - and `bin/matrix` locally - override this
35
+ # with DATABASE_URL to also exercise PostgreSQL and MySQL.
2
36
  default: &default
3
- adapter: postgresql
4
- encoding: unicode
37
+ adapter: sqlite3
5
38
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
39
+ timeout: 5000
40
+ schema_dump: <%= schema_dump %>
6
41
 
7
42
  development:
8
43
  <<: *default
9
- database: geneva_drive_development
44
+ database: storage/development.sqlite3
10
45
 
11
46
  test:
12
47
  <<: *default
13
- database: geneva_drive_test
48
+ database: <%= artifacts %>/test.sqlite3
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ # How step jobs reach your queue.
4
+ # - :active_job (the default) enqueues GenevaDrive::PerformStepJob through
5
+ # Active Job, onto whatever adapter the app uses.
6
+ # - :sidekiq pushes GenevaDrive::SidekiqPerformStepJob, a plain Sidekiq::Job,
7
+ # straight through Sidekiq::Client - for apps that write Sidekiq jobs rather
8
+ # than Active Job ones. Schedule GenevaDrive::SidekiqHousekeepingJob then.
9
+ # Supports Sidekiq 7.3 and later 7.x, and 8.x.
10
+ #
11
+ # GenevaDrive.dispatcher = :active_job
12
+
13
+ # How long to keep completed/canceled workflows before automatic deletion.
14
+ # Set to nil to disable automatic cleanup.
15
+ # When enabled, finished and canceled workflows older than this threshold
16
+ # will be deleted along with their step execution records.
17
+ #
18
+ # GenevaDrive.delete_completed_workflows_after = 30.days
19
+
20
+ # How long a step execution can be in "in_progress" state before being
21
+ # considered stuck. This typically happens when a worker process crashes
22
+ # mid-execution. The HousekeepingJob will recover these based on the
23
+ # stuck_recovery_action setting.
24
+ #
25
+ # GenevaDrive.stuck_in_progress_threshold = 1.hour
26
+
27
+ # How long a step execution can be past its scheduled_for time while
28
+ # still in "scheduled" state before being considered stuck. This can
29
+ # happen if jobs fail to enqueue or are lost by the queue backend.
30
+ #
31
+ # GenevaDrive.stuck_scheduled_threshold = 15.minutes
32
+
33
+ # Action to take when recovering stuck step executions.
34
+ # - :reattempt - Mark as recovered and schedule a retry (default)
35
+ # - :cancel - Mark as recovered and cancel the workflow
36
+ #
37
+ # GenevaDrive.stuck_recovery_action = :reattempt
38
+
39
+ # Maximum number of workflows/step executions to process in a single
40
+ # housekeeping run. Prevents runaway processing if there's a large backlog.
41
+ #
42
+ # GenevaDrive.housekeeping_batch_size = 1000
43
+
44
+ # Maximum size in bytes of a resumable step cursor once serialized to JSON.
45
+ # The cursor is a position marker (an id, page number, or pagination token),
46
+ # not a place to store the data being processed - it is rewritten on every
47
+ # checkpoint and copied to every successor execution. Exceeding the limit
48
+ # raises GenevaDrive::CursorTooLargeError. Set to nil to disable the check.
49
+ #
50
+ # GenevaDrive.max_cursor_size = 128.kilobytes
51
+
52
+ # Maximum size in bytes of a signal payload once serialized to JSON.
53
+ # A payload describes an event (an order id, an amount, a decision), not the
54
+ # data the step is going to work on - that belongs on the hero. Exceeding the
55
+ # limit raises GenevaDrive::SignalPayloadTooLargeError from Workflow#signal!,
56
+ # before anything is persisted. Set to nil to disable the check.
57
+ #
58
+ # GenevaDrive.max_signal_payload_size = 128.kilobytes
59
+
60
+ # How long a step execution may sit in the "waiting" state before
61
+ # housekeeping counts it in the geneva_drive.waiting_overdue gauge.
62
+ # Waiting indefinitely is legitimate, so this is not a timeout - it keeps
63
+ # a stalled rendezvous from going unnoticed. Alert on the overdue gauge.
64
+ # Set to nil to disable the gauge.
65
+ #
66
+ # GenevaDrive.waiting_visibility_threshold = 7.days
67
+
68
+ # Whether to defer job enqueueing until after the database transaction commits.
69
+ # In production this ensures step execution records are visible to job workers
70
+ # before the job runs. In test environments (the default when Rails.env.test?)
71
+ # this is set to false so that jobs are enqueued immediately — transactional
72
+ # tests (especially with SQLite) never commit the outermost transaction, which
73
+ # can cause after_commit callbacks to misbehave or not fire at all.
74
+ #
75
+ # Set to true in tests if you need strict transaction semantics for a specific
76
+ # test suite, or if you are not using transactional tests.
77
+ #
78
+ # GenevaDrive.enqueue_after_commit = !Rails.env.test?
@@ -0,0 +1,95 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema[8.1].define(version: 2026_10_25_000614) do
14
+ create_table "geneva_drive_signals", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
15
+ t.bigint "workflow_id", null: false
16
+ t.string "name", null: false
17
+ t.string "idempotency_key"
18
+ t.string "state", default: "pending", null: false
19
+ t.json "payload"
20
+ t.datetime "claimed_at"
21
+ t.datetime "consumed_at"
22
+ t.bigint "claimed", default: 0, null: false
23
+ t.bigint "consumed", default: 0, null: false
24
+ t.datetime "created_at", null: false
25
+ t.datetime "updated_at", null: false
26
+ t.index ["workflow_id", "name", "idempotency_key"], name: "index_geneva_drive_signals_dedup", unique: true
27
+ t.index ["workflow_id", "state"], name: "index_geneva_drive_signals_on_workflow_id_and_state"
28
+ t.index ["workflow_id"], name: "index_geneva_drive_signals_on_workflow_id"
29
+ end
30
+
31
+ create_table "geneva_drive_step_executions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
32
+ t.bigint "workflow_id", null: false
33
+ t.string "step_name", null: false
34
+ t.string "state", default: "scheduled", null: false
35
+ t.string "outcome"
36
+ t.datetime "scheduled_for", null: false
37
+ t.datetime "started_at"
38
+ t.datetime "completed_at"
39
+ t.datetime "failed_at"
40
+ t.datetime "canceled_at"
41
+ t.datetime "skipped_at"
42
+ t.string "error_class_name"
43
+ t.text "error_message"
44
+ t.text "error_backtrace"
45
+ t.string "job_id"
46
+ t.text "metadata", size: :long
47
+ t.datetime "created_at", null: false
48
+ t.datetime "updated_at", null: false
49
+ t.virtual "active_unique_key", type: :string, limit: 767, as: "(case when (`state` in (_utf8mb4'scheduled',_utf8mb4'in_progress')) then cast(`workflow_id` as char charset utf8mb4) else NULL end)", stored: true
50
+ t.datetime "finished_at"
51
+ t.json "cursor"
52
+ t.bigint "continues_from_id"
53
+ t.bigint "signal_id"
54
+ t.datetime "waiting_since"
55
+ t.index ["active_unique_key"], name: "index_geneva_drive_step_executions_one_active", unique: true
56
+ t.index ["continues_from_id"], name: "index_geneva_drive_step_executions_on_continues_from_id"
57
+ t.index ["finished_at"], name: "index_geneva_drive_step_executions_on_finished_at"
58
+ t.index ["scheduled_for"], name: "index_geneva_drive_step_executions_on_scheduled_for"
59
+ t.index ["signal_id"], name: "index_geneva_drive_step_executions_on_signal_id"
60
+ t.index ["state", "scheduled_for"], name: "index_geneva_drive_step_executions_scheduled"
61
+ t.index ["state", "started_at"], name: "index_geneva_drive_step_executions_in_progress_started_at"
62
+ t.index ["state"], name: "index_geneva_drive_step_executions_on_state"
63
+ t.index ["workflow_id", "created_at"], name: "idx_on_workflow_id_created_at_af16a14fb2"
64
+ t.index ["workflow_id", "state"], name: "index_geneva_drive_step_executions_on_workflow_id_and_state"
65
+ t.index ["workflow_id"], name: "index_geneva_drive_step_executions_on_workflow_id"
66
+ end
67
+
68
+ create_table "geneva_drive_workflows", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
69
+ t.string "type", null: false
70
+ t.string "hero_type"
71
+ t.bigint "hero_id"
72
+ t.string "state", default: "ready", null: false
73
+ t.string "current_step_name"
74
+ t.string "next_step_name"
75
+ t.boolean "allow_multiple", default: false, null: false
76
+ t.text "metadata", size: :long
77
+ t.datetime "started_at"
78
+ t.datetime "transitioned_at"
79
+ t.datetime "created_at", null: false
80
+ t.datetime "updated_at", null: false
81
+ t.virtual "ongoing_unique_key", type: :string, limit: 767, as: "(case when ((`state` not in (_utf8mb4'finished',_utf8mb4'canceled')) and (`allow_multiple` = 0)) then concat(`type`,_utf8mb4'-',`hero_type`,_utf8mb4'-',`hero_id`) else NULL end)", stored: true
82
+ t.index ["hero_type", "hero_id"], name: "index_geneva_drive_workflows_on_hero_type_and_hero_id"
83
+ t.index ["ongoing_unique_key"], name: "index_geneva_drive_workflows_unique_ongoing", unique: true
84
+ t.index ["state"], name: "index_geneva_drive_workflows_on_state"
85
+ t.index ["type"], name: "index_geneva_drive_workflows_on_type"
86
+ end
87
+
88
+ create_table "users", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
89
+ t.string "email", null: false
90
+ t.string "name"
91
+ t.boolean "active", default: true
92
+ t.datetime "created_at", null: false
93
+ t.datetime "updated_at", null: false
94
+ end
95
+ end
@@ -0,0 +1,99 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema[8.1].define(version: 2026_10_25_000614) do
14
+ # These are extensions that must be enabled in order to support this database
15
+ enable_extension "pg_catalog.plpgsql"
16
+
17
+ create_table "geneva_drive_signals", force: :cascade do |t|
18
+ t.bigint "workflow_id", null: false
19
+ t.string "name", null: false
20
+ t.string "idempotency_key"
21
+ t.string "state", default: "pending", null: false
22
+ t.jsonb "payload"
23
+ t.datetime "claimed_at"
24
+ t.datetime "consumed_at"
25
+ t.bigint "claimed", default: 0, null: false
26
+ t.bigint "consumed", default: 0, null: false
27
+ t.datetime "created_at", null: false
28
+ t.datetime "updated_at", null: false
29
+ t.index ["workflow_id", "name", "idempotency_key"], name: "index_geneva_drive_signals_dedup", unique: true
30
+ t.index ["workflow_id", "state"], name: "index_geneva_drive_signals_on_workflow_id_and_state"
31
+ t.index ["workflow_id"], name: "index_geneva_drive_signals_on_workflow_id"
32
+ end
33
+
34
+ create_table "geneva_drive_step_executions", force: :cascade do |t|
35
+ t.bigint "workflow_id", null: false
36
+ t.string "step_name", null: false
37
+ t.string "state", default: "scheduled", null: false
38
+ t.string "outcome"
39
+ t.datetime "scheduled_for", null: false
40
+ t.datetime "started_at"
41
+ t.datetime "completed_at"
42
+ t.datetime "failed_at"
43
+ t.datetime "canceled_at"
44
+ t.datetime "skipped_at"
45
+ t.string "error_class_name"
46
+ t.text "error_message"
47
+ t.text "error_backtrace"
48
+ t.string "job_id"
49
+ t.jsonb "metadata"
50
+ t.datetime "created_at", null: false
51
+ t.datetime "updated_at", null: false
52
+ t.datetime "finished_at"
53
+ t.jsonb "cursor"
54
+ t.bigint "continues_from_id"
55
+ t.bigint "signal_id"
56
+ t.datetime "waiting_since"
57
+ t.index ["continues_from_id"], name: "index_geneva_drive_step_executions_on_continues_from_id"
58
+ t.index ["finished_at"], name: "index_geneva_drive_step_executions_on_finished_at"
59
+ t.index ["scheduled_for"], name: "index_geneva_drive_step_executions_on_scheduled_for"
60
+ t.index ["signal_id"], name: "index_geneva_drive_step_executions_on_signal_id"
61
+ t.index ["started_at"], name: "index_geneva_drive_step_executions_in_progress_started_at", where: "((state)::text = 'in_progress'::text)"
62
+ t.index ["state", "scheduled_for"], name: "index_geneva_drive_step_executions_scheduled"
63
+ t.index ["state"], name: "index_geneva_drive_step_executions_on_state"
64
+ t.index ["workflow_id", "created_at"], name: "idx_on_workflow_id_created_at_af16a14fb2"
65
+ t.index ["workflow_id", "state"], name: "index_geneva_drive_step_executions_on_workflow_id_and_state"
66
+ t.index ["workflow_id"], name: "index_geneva_drive_step_executions_on_workflow_id"
67
+ t.index ["workflow_id"], name: "index_geneva_drive_step_executions_one_active", unique: true, where: "((state)::text = ANY ((ARRAY['scheduled'::character varying, 'in_progress'::character varying])::text[]))"
68
+ end
69
+
70
+ create_table "geneva_drive_workflows", force: :cascade do |t|
71
+ t.string "type", null: false
72
+ t.string "hero_type"
73
+ t.bigint "hero_id"
74
+ t.string "state", default: "ready", null: false
75
+ t.string "current_step_name"
76
+ t.string "next_step_name"
77
+ t.boolean "allow_multiple", default: false, null: false
78
+ t.jsonb "metadata"
79
+ t.datetime "started_at"
80
+ t.datetime "transitioned_at"
81
+ t.datetime "created_at", null: false
82
+ t.datetime "updated_at", null: false
83
+ t.index ["hero_type", "hero_id"], name: "index_geneva_drive_workflows_on_hero_type_and_hero_id"
84
+ t.index ["state"], name: "index_geneva_drive_workflows_on_state"
85
+ t.index ["type", "hero_type", "hero_id"], name: "index_geneva_drive_workflows_unique_ongoing", unique: true, where: "(((state)::text <> ALL ((ARRAY['finished'::character varying, 'canceled'::character varying])::text[])) AND (allow_multiple = false))"
86
+ t.index ["type"], name: "index_geneva_drive_workflows_on_type"
87
+ end
88
+
89
+ create_table "users", force: :cascade do |t|
90
+ t.string "email", null: false
91
+ t.string "name"
92
+ t.boolean "active", default: true
93
+ t.datetime "created_at", null: false
94
+ t.datetime "updated_at", null: false
95
+ end
96
+
97
+ add_foreign_key "geneva_drive_signals", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
98
+ add_foreign_key "geneva_drive_step_executions", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
99
+ end
@@ -0,0 +1,96 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema[7.2].define(version: 2026_10_25_090462) do
14
+ create_table "geneva_drive_signals", force: :cascade do |t|
15
+ t.integer "workflow_id", null: false
16
+ t.string "name", null: false
17
+ t.string "idempotency_key"
18
+ t.string "state", default: "pending", null: false
19
+ t.json "payload"
20
+ t.datetime "claimed_at"
21
+ t.datetime "consumed_at"
22
+ t.bigint "claimed", default: 0, null: false
23
+ t.bigint "consumed", default: 0, null: false
24
+ t.datetime "created_at", null: false
25
+ t.datetime "updated_at", null: false
26
+ t.index ["workflow_id", "name", "idempotency_key"], name: "index_geneva_drive_signals_dedup", unique: true
27
+ t.index ["workflow_id", "state"], name: "index_geneva_drive_signals_on_workflow_id_and_state"
28
+ t.index ["workflow_id"], name: "index_geneva_drive_signals_on_workflow_id"
29
+ end
30
+
31
+ create_table "geneva_drive_step_executions", force: :cascade do |t|
32
+ t.integer "workflow_id", null: false
33
+ t.string "step_name", null: false
34
+ t.string "state", default: "scheduled", null: false
35
+ t.string "outcome"
36
+ t.datetime "scheduled_for", null: false
37
+ t.datetime "started_at"
38
+ t.datetime "completed_at"
39
+ t.datetime "failed_at"
40
+ t.datetime "canceled_at"
41
+ t.datetime "skipped_at"
42
+ t.string "error_class_name"
43
+ t.text "error_message"
44
+ t.text "error_backtrace"
45
+ t.string "job_id"
46
+ t.text "metadata"
47
+ t.datetime "created_at", null: false
48
+ t.datetime "updated_at", null: false
49
+ t.datetime "finished_at"
50
+ t.json "cursor"
51
+ t.bigint "continues_from_id"
52
+ t.bigint "signal_id"
53
+ t.datetime "waiting_since"
54
+ t.index ["continues_from_id"], name: "index_geneva_drive_step_executions_on_continues_from_id"
55
+ t.index ["finished_at"], name: "index_geneva_drive_step_executions_on_finished_at"
56
+ t.index ["scheduled_for"], name: "index_geneva_drive_step_executions_on_scheduled_for"
57
+ t.index ["signal_id"], name: "index_geneva_drive_step_executions_on_signal_id"
58
+ t.index ["started_at"], name: "index_geneva_drive_step_executions_in_progress_started_at", where: "state = 'in_progress'"
59
+ t.index ["state", "scheduled_for"], name: "index_geneva_drive_step_executions_scheduled"
60
+ t.index ["state"], name: "index_geneva_drive_step_executions_on_state"
61
+ t.index ["workflow_id", "created_at"], name: "idx_on_workflow_id_created_at_af16a14fb2"
62
+ t.index ["workflow_id", "state"], name: "index_geneva_drive_step_executions_on_workflow_id_and_state"
63
+ t.index ["workflow_id"], name: "index_geneva_drive_step_executions_on_workflow_id"
64
+ t.index ["workflow_id"], name: "index_geneva_drive_step_executions_one_active", unique: true, where: "state IN ('scheduled', 'in_progress')"
65
+ end
66
+
67
+ create_table "geneva_drive_workflows", force: :cascade do |t|
68
+ t.string "type", null: false
69
+ t.string "hero_type"
70
+ t.integer "hero_id"
71
+ t.string "state", default: "ready", null: false
72
+ t.string "current_step_name"
73
+ t.string "next_step_name"
74
+ t.boolean "allow_multiple", default: false, null: false
75
+ t.text "metadata"
76
+ t.datetime "started_at"
77
+ t.datetime "transitioned_at"
78
+ t.datetime "created_at", null: false
79
+ t.datetime "updated_at", null: false
80
+ t.index ["hero_type", "hero_id"], name: "index_geneva_drive_workflows_on_hero_type_and_hero_id"
81
+ t.index ["state"], name: "index_geneva_drive_workflows_on_state"
82
+ t.index ["type", "hero_type", "hero_id"], name: "index_geneva_drive_workflows_unique_ongoing", unique: true, where: "state NOT IN ('finished', 'canceled') AND allow_multiple = 0"
83
+ t.index ["type"], name: "index_geneva_drive_workflows_on_type"
84
+ end
85
+
86
+ create_table "users", force: :cascade do |t|
87
+ t.string "email", null: false
88
+ t.string "name"
89
+ t.boolean "active", default: true
90
+ t.datetime "created_at", null: false
91
+ t.datetime "updated_at", null: false
92
+ end
93
+
94
+ add_foreign_key "geneva_drive_signals", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
95
+ add_foreign_key "geneva_drive_step_executions", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
96
+ end
@@ -0,0 +1,96 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema[8.0].define(version: 2026_10_25_000614) do
14
+ create_table "geneva_drive_signals", force: :cascade do |t|
15
+ t.integer "workflow_id", null: false
16
+ t.string "name", null: false
17
+ t.string "idempotency_key"
18
+ t.string "state", default: "pending", null: false
19
+ t.json "payload"
20
+ t.datetime "claimed_at"
21
+ t.datetime "consumed_at"
22
+ t.bigint "claimed", default: 0, null: false
23
+ t.bigint "consumed", default: 0, null: false
24
+ t.datetime "created_at", null: false
25
+ t.datetime "updated_at", null: false
26
+ t.index ["workflow_id", "name", "idempotency_key"], name: "index_geneva_drive_signals_dedup", unique: true
27
+ t.index ["workflow_id", "state"], name: "index_geneva_drive_signals_on_workflow_id_and_state"
28
+ t.index ["workflow_id"], name: "index_geneva_drive_signals_on_workflow_id"
29
+ end
30
+
31
+ create_table "geneva_drive_step_executions", force: :cascade do |t|
32
+ t.integer "workflow_id", null: false
33
+ t.string "step_name", null: false
34
+ t.string "state", default: "scheduled", null: false
35
+ t.string "outcome"
36
+ t.datetime "scheduled_for", null: false
37
+ t.datetime "started_at"
38
+ t.datetime "completed_at"
39
+ t.datetime "failed_at"
40
+ t.datetime "canceled_at"
41
+ t.datetime "skipped_at"
42
+ t.string "error_class_name"
43
+ t.text "error_message"
44
+ t.text "error_backtrace"
45
+ t.string "job_id"
46
+ t.text "metadata"
47
+ t.datetime "created_at", null: false
48
+ t.datetime "updated_at", null: false
49
+ t.datetime "finished_at"
50
+ t.json "cursor"
51
+ t.bigint "continues_from_id"
52
+ t.bigint "signal_id"
53
+ t.datetime "waiting_since"
54
+ t.index ["continues_from_id"], name: "index_geneva_drive_step_executions_on_continues_from_id"
55
+ t.index ["finished_at"], name: "index_geneva_drive_step_executions_on_finished_at"
56
+ t.index ["scheduled_for"], name: "index_geneva_drive_step_executions_on_scheduled_for"
57
+ t.index ["signal_id"], name: "index_geneva_drive_step_executions_on_signal_id"
58
+ t.index ["started_at"], name: "index_geneva_drive_step_executions_in_progress_started_at", where: "state = 'in_progress'"
59
+ t.index ["state", "scheduled_for"], name: "index_geneva_drive_step_executions_scheduled"
60
+ t.index ["state"], name: "index_geneva_drive_step_executions_on_state"
61
+ t.index ["workflow_id", "created_at"], name: "idx_on_workflow_id_created_at_af16a14fb2"
62
+ t.index ["workflow_id", "state"], name: "index_geneva_drive_step_executions_on_workflow_id_and_state"
63
+ t.index ["workflow_id"], name: "index_geneva_drive_step_executions_on_workflow_id"
64
+ t.index ["workflow_id"], name: "index_geneva_drive_step_executions_one_active", unique: true, where: "state IN ('scheduled', 'in_progress')"
65
+ end
66
+
67
+ create_table "geneva_drive_workflows", force: :cascade do |t|
68
+ t.string "type", null: false
69
+ t.string "hero_type"
70
+ t.integer "hero_id"
71
+ t.string "state", default: "ready", null: false
72
+ t.string "current_step_name"
73
+ t.string "next_step_name"
74
+ t.boolean "allow_multiple", default: false, null: false
75
+ t.text "metadata"
76
+ t.datetime "started_at"
77
+ t.datetime "transitioned_at"
78
+ t.datetime "created_at", null: false
79
+ t.datetime "updated_at", null: false
80
+ t.index ["hero_type", "hero_id"], name: "index_geneva_drive_workflows_on_hero_type_and_hero_id"
81
+ t.index ["state"], name: "index_geneva_drive_workflows_on_state"
82
+ t.index ["type", "hero_type", "hero_id"], name: "index_geneva_drive_workflows_unique_ongoing", unique: true, where: "state NOT IN ('finished', 'canceled') AND allow_multiple = 0"
83
+ t.index ["type"], name: "index_geneva_drive_workflows_on_type"
84
+ end
85
+
86
+ create_table "users", force: :cascade do |t|
87
+ t.string "email", null: false
88
+ t.string "name"
89
+ t.boolean "active", default: true
90
+ t.datetime "created_at", null: false
91
+ t.datetime "updated_at", null: false
92
+ end
93
+
94
+ add_foreign_key "geneva_drive_signals", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
95
+ add_foreign_key "geneva_drive_step_executions", "geneva_drive_workflows", column: "workflow_id", on_delete: :cascade
96
+ end