geneva_drive 0.4.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/MANUAL.md +1037 -355
- data/README.md +3 -1
- data/Rakefile +13 -2
- data/bin/md2html +96 -19
- data/lib/generators/geneva_drive/install/install_generator.rb +25 -0
- data/lib/generators/geneva_drive/install/templates/add_metadata_to_step_executions.rb +17 -0
- data/lib/generators/geneva_drive/install/templates/add_metadata_to_workflows.rb +17 -0
- data/lib/generators/geneva_drive/install/templates/add_resumable_step_support.rb +29 -0
- data/lib/generators/geneva_drive/install/templates/add_started_at_index_to_step_executions.rb +48 -0
- data/lib/generators/geneva_drive/install/templates/allow_null_hero_on_workflows.rb +66 -0
- data/lib/generators/geneva_drive/install/templates/create_step_executions_migration.rb +25 -5
- data/lib/generators/geneva_drive/install/templates/create_workflows_migration.rb +14 -3
- data/lib/generators/geneva_drive/install/templates/initializer.rb.tt +22 -2
- data/lib/geneva_drive/combined_exception_policy.rb +106 -0
- data/lib/geneva_drive/exception_policy.rb +354 -0
- data/lib/geneva_drive/executor.rb +430 -148
- data/lib/geneva_drive/flow_control.rb +82 -11
- data/lib/geneva_drive/iterable_step.rb +199 -0
- data/lib/geneva_drive/job_options.rb +70 -0
- data/lib/geneva_drive/jobs/housekeeping_job.rb +174 -52
- data/lib/geneva_drive/jobs/perform_step_job.rb +91 -3
- data/lib/geneva_drive/migration_helpers.rb +55 -20
- data/lib/geneva_drive/resumable_step_definition.rb +97 -0
- data/lib/geneva_drive/step_definition.rb +160 -40
- data/lib/geneva_drive/step_execution/metadata_accessor.rb +121 -0
- data/lib/geneva_drive/step_execution.rb +124 -2
- data/lib/geneva_drive/test_helpers.rb +123 -4
- data/lib/geneva_drive/version.rb +1 -1
- data/lib/geneva_drive/workflow/metadata_accessor.rb +85 -0
- data/lib/geneva_drive/workflow.rb +485 -73
- data/lib/geneva_drive.rb +100 -2
- data/test/dsl/class_level_on_exception_test.rb +200 -0
- data/test/dsl/exception_policy_test.rb +331 -0
- data/test/dsl/step_definition_test.rb +126 -2
- data/test/dsl/step_level_exception_policy_test.rb +143 -0
- data/test/dummy/config/storage.yml +7 -0
- data/test/dummy_install/config/storage.yml +7 -0
- data/test/jobs/housekeeping_job_test.rb +451 -5
- data/test/jobs/perform_step_job_double_deferral_test.rb +268 -0
- data/test/jobs/perform_step_job_test.rb +25 -2
- data/test/migration_helpers_test.rb +137 -0
- data/test/step_execution/metadata_accessor_test.rb +147 -0
- data/test/test_helper.rb +37 -0
- data/test/test_helper_test.rb +281 -0
- data/test/workflow/class_level_on_exception_integration_test.rb +313 -0
- data/test/workflow/composable_exception_policy_test.rb +231 -0
- data/test/workflow/cursor_size_limit_test.rb +75 -0
- data/test/workflow/executor_test.rb +100 -4
- data/test/workflow/external_cancel_test.rb +245 -0
- data/test/workflow/flow_control_test.rb +4 -3
- data/test/workflow/instrumentation_test.rb +28 -0
- data/test/workflow/max_reattempts_test.rb +51 -0
- data/test/workflow/pause_resume_test.rb +574 -0
- data/test/workflow/reattempt_on_exception_integration_test.rb +68 -0
- data/test/workflow/resumable_step_integration_test.rb +341 -0
- data/test/workflow/resumable_step_test.rb +615 -0
- data/test/workflow/resumable_without_migration_test.rb +103 -0
- data/test/workflow/resume_and_skip_test.rb +57 -41
- data/test/workflow/with_inline_enqueue_test.rb +137 -0
- data/test/workflow/workflow_test.rb +117 -2
- metadata +47 -61
- data/test/dummy/db/migrate/20241217000001_create_geneva_drive_workflows.rb +0 -68
- data/test/dummy/db/migrate/20241217000002_create_geneva_drive_step_executions.rb +0 -90
- data/test/dummy/db/migrate/20241217000003_add_finished_at_to_geneva_drive_step_executions.rb +0 -25
- data/test/dummy/db/migrate/20241217000004_add_error_class_name_to_geneva_drive_step_executions.rb +0 -7
- data/test/dummy/db/schema.rb +0 -72
- data/test/dummy/log/development.log +0 -112
- data/test/dummy/log/test.log +0 -4
- data/test/dummy/tmp/local_secret.txt +0 -1
- data/test/dummy_install/Rakefile +0 -6
- data/test/dummy_install/app/assets/stylesheets/application.css +0 -15
- data/test/dummy_install/app/controllers/application_controller.rb +0 -4
- data/test/dummy_install/app/helpers/application_helper.rb +0 -2
- data/test/dummy_install/app/jobs/application_job.rb +0 -7
- data/test/dummy_install/app/models/application_record.rb +0 -3
- data/test/dummy_install/app/models/user.rb +0 -9
- data/test/dummy_install/app/views/layouts/application.html.erb +0 -27
- data/test/dummy_install/app/views/layouts/mailer.html.erb +0 -13
- data/test/dummy_install/app/views/layouts/mailer.text.erb +0 -1
- data/test/dummy_install/app/views/pwa/manifest.json.erb +0 -22
- data/test/dummy_install/app/views/pwa/service-worker.js +0 -26
- data/test/dummy_install/bin/dev +0 -2
- data/test/dummy_install/bin/rails +0 -4
- data/test/dummy_install/bin/rake +0 -4
- data/test/dummy_install/bin/setup +0 -34
- data/test/dummy_install/config/application.rb +0 -29
- data/test/dummy_install/config/boot.rb +0 -5
- data/test/dummy_install/config/cable.yml +0 -10
- data/test/dummy_install/config/database.yml +0 -13
- data/test/dummy_install/config/environment.rb +0 -5
- data/test/dummy_install/config/environments/development.rb +0 -51
- data/test/dummy_install/config/environments/production.rb +0 -70
- data/test/dummy_install/config/environments/test.rb +0 -39
- data/test/dummy_install/config/initializers/assets.rb +0 -7
- data/test/dummy_install/config/initializers/content_security_policy.rb +0 -25
- data/test/dummy_install/config/initializers/filter_parameter_logging.rb +0 -8
- data/test/dummy_install/config/initializers/geneva_drive.rb +0 -76
- data/test/dummy_install/config/initializers/inflections.rb +0 -16
- data/test/dummy_install/config/locales/en.yml +0 -31
- data/test/dummy_install/config/puma.rb +0 -38
- data/test/dummy_install/config/routes.rb +0 -3
- data/test/dummy_install/config.ru +0 -6
- data/test/dummy_install/db/migrate/20241217000000_create_users.rb +0 -12
- data/test/dummy_install/db/migrate/20260128104738_create_geneva_drive_workflows.rb +0 -74
- data/test/dummy_install/db/migrate/20260128104739_create_geneva_drive_step_executions.rb +0 -97
- data/test/dummy_install/db/migrate/20260128104740_add_finished_at_to_geneva_drive_step_executions.rb +0 -25
- data/test/dummy_install/db/migrate/20260128104741_add_error_class_name_to_geneva_drive_step_executions.rb +0 -7
- data/test/dummy_install/db/migrate/20260128104742_add_resumable_step_support_to_geneva_drive_step_executions.rb +0 -25
- data/test/dummy_install/db/schema.rb +0 -76
- data/test/dummy_install/log/test.log +0 -4031
- data/test/dummy_install/public/400.html +0 -114
- data/test/dummy_install/public/404.html +0 -114
- data/test/dummy_install/public/406-unsupported-browser.html +0 -114
- data/test/dummy_install/public/422.html +0 -114
- data/test/dummy_install/public/500.html +0 -114
- data/test/dummy_install/public/icon.png +0 -0
- data/test/dummy_install/public/icon.svg +0 -3
- data/test/dummy_install/tmp/local_secret.txt +0 -1
- data/test/generators/install_generator_test.rb +0 -94
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a4a73216fcf8355550c3b5e26e76eee14468eb301e8eb315a979bc8f34bdb670
|
|
4
|
+
data.tar.gz: a9b9cc0d8998f6c479806158dd12725774431e7dba8486c5781ed68f238175b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b77f44d451d256d081af6d7b30d62bcaa46bfd41b0606cdcd1d07529e18db7ff5e2d1753d52f6bd24f600f92b7e14587146072a53e46e1052728c760cf6ded78
|
|
7
|
+
data.tar.gz: 9bdd9f474cd8370ebcc77b4126617833a249b6e1237ed8021463b66d082db63e17bc024fa8718ec3aee1d031731c72b3a9c90903d2ce40ced2f01eb0b1c421ef
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.6.0]
|
|
6
|
+
|
|
7
|
+
- Add resumable steps for cursor-based iteration over large collections. `resumable_step :name do |iter| ... end` passes an `IterableStep` (API-compatible with Rails 8.1's `ActiveJob::Continuation::Step`) whose cursor is checkpointed to the database, so the step can be interrupted — by `max_iterations:`, `max_runtime:`, queue shutdown, external pause/cancel, or explicit `suspend!`/`skip_to!` — and continued in a later job execution from where it left off. Interruption completes the current step execution with outcome `continued` and creates a successor execution linked via `continues_from_id`, keeping a full audit chain instead of introducing a suspended state. Resumable steps run through the same `Executor` as regular steps: exception policies (step- and class-level, `max_reattempts`, `terminal_action:`, `report:`), tagged/injected loggers, `Rails.error` context, and merged job options all apply, with `reattempt!` and `resume!` continuing from the last checkpoint (use `reattempt!(rewind: true)` to start over). `HousekeepingJob` recovery is cursor-aware. The feature needs two new columns (`cursor`, `continues_from_id`) added by the installer migration; until it runs, everything else degrades gracefully (regular steps, pause/resume and housekeeping are unaffected) and executing an actual `resumable_step` raises a configuration error pointing at the missing migration. Cursors are limited to 128 KB of serialized JSON by default — the cursor is a position marker, not a payload — configurable via `GenevaDrive.max_cursor_size` (`nil` disables the check). Test helpers: `speedrun_current_step` follows the execution chain, `run_iterations(workflow, count:)` exercises partial progress, plus `assert_cursor` and `assert_step_has_successor`.
|
|
8
|
+
- Validate `job_options` uniformly across `set_step_job_options`, per-step `job_options:`, and per-instance `workflow.step_job_options=`. Only the keys Active Job's `set` actually consumes (`:queue`, `:priority`, `:wait`, `:wait_until`) are accepted, and values are type-checked. Typos like `piority:` now raise `StepConfigurationError` at configuration time instead of being silently ignored.
|
|
9
|
+
- Add partial index on `geneva_drive_step_executions(started_at) WHERE state = 'in_progress'` and order `HousekeepingJob` recovery batches by the filter column (`started_at` for stuck-in-progress, `scheduled_for` for stuck-scheduled). Fixes `HousekeepingJob` timing out on large in-progress sets where most rows are fresh — the planner previously walked the whole `state = 'in_progress'` set looking for old rows and tripped the caller's `statement_timeout`. Fixes AppSignal incident b9a7d1d1.
|
|
10
|
+
- Add a normalized `geneva_drive.paused_ratio` gauge emitted by `HousekeepingJob`. For each workflow class (STI `type`) it reports a float in `0.0..1.0`, tagged `workflow: <ClassName>`, equal to that class's `paused` count divided by its total population (all states). Unlike absolute paused counts, this is bounded and easy to alert on (50 paused of 50 is a fire; 50 of 500,000 is noise). Derived from the existing single grouped query — no extra database load.
|
|
11
|
+
- Add optional `metadata` JSON column to workflows (mirrors the existing step executions pattern). Exposes `step_job_options` / `step_job_options=` for per-instance job option overrides (queue, priority, etc.) that merge with class-level `set_step_job_options` and persist across step boundaries. Dedupe is unaffected since metadata is not part of the uniqueness constraint.
|
|
12
|
+
- Add GenevaDrive workflow and step metadata to `Rails.error` execution context when steps execute, so Rails error reports include workflow id/class, step execution id/name, and hero identifiers.
|
|
13
|
+
- Add per-step Active Job option overrides with `step :name, job_options: { queue: :critical, priority: 0 }`. Step options take precedence over workflow-level `set_step_job_options` and per-instance options, and are preserved across reattempts and resume re-enqueueing.
|
|
14
|
+
- Add `report:` option to exception policies and class-level `on_exception` for controlling when exceptions are reported to `Rails.error.report`. Accepts `:always` (default — preserves existing behavior), `:never` (suppress reporting for expected exceptions like rate limits), or `:terminal_only` (suppress during reattempts, report only when `terminal_action` fires). The executor now defers error reporting until after policy resolution.
|
|
15
|
+
|
|
16
|
+
## [0.5.0]
|
|
17
|
+
|
|
18
|
+
- Add `ongoing?` predicate and model-level uniqueness validation for ongoing workflows. Mirrors the database unique index on `(type, hero_type, hero_id)` so that `Workflow.create` (without bang) returns validation errors instead of raising a database constraint violation.
|
|
19
|
+
- Allow `hero` to be NULL on workflows. The model declares `belongs_to :hero, optional: true` but the migration enforced `NOT NULL` on `hero_type` and `hero_id`. New installs get nullable columns; existing installs get an upgrade migration (Postgres/MySQL via `change_column_null`, SQLite via atomic table swap).
|
|
20
|
+
- Detect dominant UUID default function from application tables when generating migrations. When the host app uses a custom UUID default (e.g. `uuidv7()` instead of `gen_random_uuid()`), GenevaDrive tables now inherit the same default by quorum from existing tables.
|
|
21
|
+
- Add class-level `on_exception` for declaring exception policies that apply to all steps in a workflow. Supports blanket policies, exception-class-specific policies, and imperative block handlers. Step-level `on_exception:` overrides class-level policies.
|
|
22
|
+
- Add `GenevaDrive::ExceptionPolicy` value object for reusable exception handling configuration. Can be passed directly to `on_exception:` at step level or class level.
|
|
23
|
+
- Add `terminal_action:` option to `on_exception: :reattempt!` to control what happens when `max_reattempts` is exceeded. Accepts `:pause!` (default), `:cancel!`, or `:skip!` (to skip the failed step and continue the workflow).
|
|
24
|
+
- Add optional `metadata` JSON column to step executions for structured data (reattempt reasons, exception info). The column is optional — writing is a silent no-op if the migration has not been applied yet.
|
|
25
|
+
- Double-write exception info (class, message, backtrace) into step execution metadata alongside the dedicated error columns, preparing for future column removal.
|
|
26
|
+
- Add injectable logger support to `Executor.execute!`. Callers (background jobs, controllers) can pass a `logger:` parameter to inject a pre-tagged logger as the base for all workflow logging during step execution. The injected logger will have workflow and step-specific tags added on top. `PerformStepJob` now passes its own logger to the executor.
|
|
27
|
+
- Add `GenevaDrive.enqueue_after_commit` configuration setting to control job enqueueing behavior. When `true` (default in production), jobs are enqueued via `after_all_transactions_commit` to ensure records are visible to workers. When `false` (default in test), jobs are enqueued inline to avoid issues with transactional tests that never commit.
|
|
28
|
+
- Fix MySQL compatibility in `HousekeepingJob`: wrap LIMIT subqueries in an extra SELECT (MySQL doesn't support LIMIT in IN subqueries), and interpolate LIMIT values directly (MySQL doesn't handle bind parameters for LIMIT clauses).
|
|
29
|
+
- Fix MySQL foreign key compatibility in generator: add foreign keys separately using `add_foreign_key` and skip for MySQL due to unsigned/signed bigint type mismatch.
|
|
30
|
+
- Fix Rails 8 deprecation: use `false` instead of `:never` for `enqueue_after_transaction_commit` setting.
|
|
31
|
+
- Fix double-deferral bug where `PerformStepJob` could silently fail to enqueue with SolidQueue and other adapters that opt into `enqueue_after_transaction_commit`. When `perform_later` was called from inside an `after_all_transactions_commit` callback, ActiveJob could see the transaction as still "open" and defer the queue INSERT into a second callback that never fires — leaving the step execution with a `job_id` but no corresponding job in the queue backend. Fix: set `enqueue_after_transaction_commit = :never` on `PerformStepJob` (GenevaDrive already handles its own transaction-awareness) and add a poll-retry on step execution lookup as defense-in-depth against replication lag.
|
|
32
|
+
- Reduce default `stuck_scheduled_threshold` from 1 hour to 15 minutes for faster recovery when jobs are lost.
|
|
33
|
+
- Change `pause!` to preserve scheduled step executions instead of canceling them. Previously, calling `pause!` would cancel the scheduled execution with outcome "workflow_paused". Now, the scheduled execution remains in "scheduled" state, making it visible in the timeline as "overdue" if time passes while paused. On `resume!`, the same execution is re-enqueued (or a new one created only if the executor canceled it while paused).
|
|
34
|
+
- Improve `HousekeepingJob` to process all eligible records by looping through batches instead of stopping after the first batch. Also uses efficient SQL DELETEs with INNER JOIN for step executions cleanup and fixes cutoff times at the start of each operation for deterministic behavior.
|
|
35
|
+
- Add `GenevaDrive.with_inline_enqueue` block method for bulk workflow creation. Temporarily disables deferred job enqueueing so that bulk enqueueing libraries (e.g., BulkEnqueue) can capture and batch job inserts. Only recommended for use with co-committing, database-backed ActiveJob adapters (SolidQueue, GoodJob, Gouda) on the same database.
|
|
36
|
+
- Extract `CombinedExceptionPolicy` to encapsulate composable exception policy resolution. Multiple policies can be combined in an array — resolution walks them in order and enforces a global reattempt cap (minimum `max_reattempts` across all constituents). Both declarative and imperative (block) policies support `matching:` for targeting specific exception classes.
|
|
37
|
+
- Add workflow state gauges via Measurometer. The housekeeping job now reports workflow count gauges (`geneva_drive.<state>`) grouped by state and workflow class.
|
|
38
|
+
- Fix metadata accessor persisting Hashes via text-type `#to_s` instead of JSON serialization. Instances loaded before the lazy attribute type registration would silently corrupt metadata on save.
|
|
39
|
+
- Fix step code logger to include step execution tags. Previously, calling `logger` inside step code returned the workflow-tagged logger without execution_id and step_name tags.
|
|
40
|
+
- Clarify executor log messages — replace opaque "dropping through" phrasing with specific, actionable messages.
|
|
41
|
+
- Allow `GenevaDrive::Workflow.cancel!` to be called outside steps as well - previously it could only be called from within a step.
|
|
42
|
+
|
|
5
43
|
## [0.4.0]
|
|
6
44
|
|
|
7
45
|
- Preserve original scheduled time when resuming a paused workflow. When a workflow with a future-scheduled step is paused and then resumed before that time, the step is rescheduled for the original time (not run immediately). If the original time has passed, the step runs immediately.
|