redis-objects-periodical 0.7.0 → 0.8.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/skills/dependabot-batch-maintenance/SKILL.md +141 -0
  3. data/.github/dependabot.yml +32 -3
  4. data/.github/scripts/dependabot-auto-merge.cjs +182 -0
  5. data/.github/scripts/dependabot-auto-merge.test.cjs +278 -0
  6. data/.github/scripts/dependabot-policy.cjs +132 -0
  7. data/.github/scripts/dependabot-policy.test.cjs +235 -0
  8. data/.github/scripts/timezone-inventory.cjs +48 -0
  9. data/.github/scripts/timezone-inventory.test.cjs +86 -0
  10. data/.github/workflows/checks.yml +124 -0
  11. data/.github/workflows/ci.yml +191 -0
  12. data/.github/workflows/dependabot-auto-merge.yml +61 -0
  13. data/.github/workflows/release.yml +59 -0
  14. data/.github/workflows/rubocop_challenge.yml +71 -0
  15. data/.rspec +1 -0
  16. data/.rubocop.yml +1 -1
  17. data/.rubocop_todo.yml +16 -16
  18. data/AGENTS.md +133 -0
  19. data/CHANGELOG.md +47 -0
  20. data/CONTRIBUTING.md +203 -0
  21. data/Dockerfile +3 -2
  22. data/Gemfile +6 -6
  23. data/Gemfile.lock +70 -65
  24. data/README.md +58 -221
  25. data/docker-compose.yml +1 -3
  26. data/docs/maintaining.md +237 -0
  27. data/docs/usage.md +216 -0
  28. data/docs/verification/2026-10-27-timezone.md +25 -0
  29. data/gemfiles/active_support/Gemfile +5 -0
  30. data/gemfiles/active_support/Gemfile.lock +137 -0
  31. data/lib/redis/objects/periodical/version.rb +1 -1
  32. data/lib/redis/objects/periodical_counters.rb +1 -1
  33. data/lib/redis/objects/periodical_hashes.rb +1 -1
  34. data/lib/redis/objects/periodical_sets.rb +1 -1
  35. data/lib/redis/objects/periodical_values.rb +1 -1
  36. data/lib/redis/periodical_counter.rb +1 -1
  37. data/lib/redis/periodical_hash_key.rb +1 -1
  38. data/lib/redis/periodical_set.rb +1 -1
  39. data/lib/redis/periodical_value.rb +1 -1
  40. data/lib/redis/recurring_at_intervals/weekly.rb +3 -1
  41. data/lib/redis/recurring_at_intervals.rb +3 -3
  42. data/redis-objects-periodical.gemspec +1 -1
  43. metadata +26 -8
  44. data/.circleci/config.yml +0 -212
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,203 @@
1
+ # Contributing
2
+
3
+ Report bugs and propose improvements through
4
+ [GitHub Issues](https://github.com/ryz310/redis-objects-periodical/issues), with a
5
+ reproduction and expected behavior where possible. Link implementation PRs to
6
+ their issues and include verification results. Follow the
7
+ [code of conduct](CODE_OF_CONDUCT.md).
8
+
9
+ ## Development
10
+
11
+ Use Docker with the Compose plugin (`docker compose`). The development image uses
12
+ Ruby 3.3 and Bundler 4.0.23, matching the current minimum Ruby version and lockfile.
13
+ Agent workflow, compatibility constraints, and validation rules are documented in
14
+ [AGENTS.md](AGENTS.md).
15
+
16
+ The specs connect to the Compose service at `redis:6379` and run `flushdb` after
17
+ every example. Use a disposable Redis instance dedicated to this checkout. The
18
+ Redis service is accessible only inside its Compose network; no host port is
19
+ published. Do not redirect tests to an existing Redis containing data you want
20
+ to keep.
21
+
22
+ Development Redis uses
23
+ `redis:latest@sha256:97ec88159e6d7cfb5ddef9015386087d58a912de80112f06ebdfa78fa5989901`,
24
+ the same immutable image digest as the Redis services in
25
+ [checks.yml](.github/workflows/checks.yml) and [ci.yml](.github/workflows/ci.yml).
26
+ The digest fixes the image contents even if the `latest` tag moves.
27
+
28
+ To update Redis, review the intended server version and release notes, then
29
+ update the image reference in `docker-compose.yml`, both CI workflows, and this
30
+ paragraph together. Pull the selected image with `docker compose pull redis`,
31
+ recreate the disposable service with `docker compose up -d redis`, and run the
32
+ full specs below. Validate workflow edits with actionlint and zizmor, and check
33
+ the Ruby matrix and optional ActiveSupport job in CI before merging.
34
+
35
+ Build the development image, start Redis, and install the locked dependencies:
36
+
37
+ ```sh
38
+ docker compose build ruby
39
+ docker compose up -d redis
40
+ docker compose exec redis redis-cli ping
41
+ docker compose run --rm ruby bundle install
42
+ ```
43
+
44
+ Wait for `redis-cli ping` to return `PONG` before running specs.
45
+
46
+ ```sh
47
+ # Run the full suite, or replace the path with an affected spec.
48
+ docker compose run --rm ruby bundle exec rspec
49
+ docker compose run --rm ruby bundle exec rspec spec/lib/redis/daily_counter_spec.rb
50
+
51
+ # Check style without automatically modifying source files.
52
+ docker compose run --rm ruby bundle exec rubocop
53
+
54
+ # Build the gem or generate API documentation when relevant.
55
+ docker compose run --rm ruby bundle exec rake build
56
+ docker compose run --rm ruby bundle exec yardoc
57
+ ```
58
+
59
+ Rebuild the image and run `bundle install` again after dependency changes. Stop
60
+ the development services with `docker compose down`; routine cleanup does not
61
+ require deleting the bundle volume.
62
+
63
+ For parallel checkouts, choose a distinct Compose project name in each checkout's
64
+ shell before running any of the commands above:
65
+
66
+ ```sh
67
+ # Use a different name in each checkout, for example rop-feature-b in the second.
68
+ export COMPOSE_PROJECT_NAME=rop-feature-a
69
+ ```
70
+
71
+ Keep that name for every Compose command in the same checkout, including
72
+ `docker compose down`. Compose then creates separate Redis services, networks,
73
+ and bundle volumes for each project. Do not give those resources a shared name
74
+ or attach another project's Redis to the test network. Run specs inside the
75
+ `ruby` service so the internal `redis:6379` endpoint resolves to that project's
76
+ disposable Redis. For an interactive Redis client, use
77
+ `docker compose exec redis redis-cli`; host-port publishing is unnecessary.
78
+
79
+ ## Continuous integration
80
+
81
+ GitHub Actions runs on pull requests, pushes to `master` and `release`, and manual
82
+ dispatch. The reusable `checks.yml` workflow runs the full specs and builds the
83
+ gem on Ruby 3.3, 3.4, and 4.0, plus RuboCop on Ruby 3.3. It retains the
84
+ CircleCI Ruby images and a disposable, job-local `redis` service without
85
+ publishing host ports. Bundler 4.0.23 installs the locked dependencies without
86
+ dependency caches. Git trusts only the current workspace inside the container,
87
+ and each build verifies that the gem includes every Ruby source under `lib/`.
88
+ `ci.yml` also generates YARD documentation on Ruby 3.3, tests the Dependabot
89
+ policy without API writes (`dependabot-policy`), and runs an isolated optional
90
+ ActiveSupport integration job on Ruby 3.3 (`active-support-timezone`).
91
+
92
+ SimpleCov generates HTML and JSON coverage reports during specs. Workflow
93
+ artifacts are kept for 14 days and include JUnit results, per-Ruby coverage,
94
+ built gems, and YARD documentation. Test results and available coverage are saved
95
+ even when specs fail. The `upload-coverage` job waits for the reusable checks,
96
+ requires all three Ruby result files, and saves the combined
97
+ `coverage/coverage.json` as the `merged-coverage` artifact.
98
+
99
+ Coverage credentials and quality-gate administration are described in
100
+ [Maintaining](docs/maintaining.md#coverage-reporting).
101
+
102
+ Third-party and GitHub-owned Actions are pinned to full commit SHAs, with version
103
+ comments. Dependabot checks for Actions updates weekly. Routine CI uses read-only
104
+ repository permissions and does not persist checkout credentials.
105
+
106
+ ## Optional ActiveSupport integration
107
+
108
+ The regular suite excludes `spec/integration/**/*_spec.rb` so its plain Ruby
109
+ checks do not load ActiveSupport. The separate `active-support-timezone` job runs
110
+ on the minimum supported Ruby (currently 3.3), using
111
+ `gemfiles/active_support/Gemfile` and its own `Gemfile.lock`. ActiveSupport is an
112
+ optional integration-test dependency; the gem remains usable without Rails or
113
+ ActiveSupport. This job verifies the selected Ruby/ActiveSupport pair; other
114
+ Rails releases and Ruby/ActiveSupport combinations are outside its verified scope.
115
+
116
+ ### ActiveSupport version selection
117
+
118
+ Select the newest stable ActiveSupport minor series that accepts the gem's
119
+ minimum Ruby version and is still covered by upstream security maintenance at
120
+ the time of review. Revisit the selection as Ruby requirements and upstream
121
+ maintenance change. Check both the
122
+ [RubyGems Ruby requirement](https://rubygems.org/gems/activesupport) and the
123
+ [Rails maintenance policy](https://rubyonrails.org/maintenance) when selecting
124
+ or reviewing the series.
125
+
126
+ As reviewed on 2026-10-27, the selected series is **ActiveSupport 8.1** with
127
+ `gem 'activesupport', '~> 8.1.4'`. Version
128
+ [8.1.4 requires Ruby >= 3.2.0](https://rubygems.org/gems/activesupport/versions/8.1.4),
129
+ so it can be tested on the Ruby 3.3 baseline. The upstream policy currently lists
130
+ security maintenance for 8.1 through 2027-10-10. Regular bug-fix maintenance
131
+ follows a separate support window.
132
+
133
+ The constraint allows `>= 8.1.4, < 8.2`, including later patches/security releases
134
+ in 8.1. The separate lockfile pins the exact tested release (currently 8.1.4),
135
+ and CI installs that lockfile with `BUNDLE_FROZEN=true`; CI does not resolve a new
136
+ version on every run. Raising the patch floor or selecting another minor/major
137
+ series requires an explicit dependency change and successful validation.
138
+
139
+ Review this choice when the Ruby minimum changes, a newer compatible stable
140
+ series is released, or the selected series approaches the end of security
141
+ maintenance. Update the constraint and optional lockfile together, retain
142
+ unrelated baseline dependencies where compatible, and run the isolated timezone
143
+ suite, the plain-Ruby suite, RuboCop, and the gem build. Record the reviewed date,
144
+ chosen release, and upstream sources in this section; report verification results
145
+ in the PR. Compatibility with older ActiveSupport releases remains outside this
146
+ job's verified scope. The optional dependency does not change
147
+ the gemspec's Ruby requirement or add an ActiveSupport runtime dependency.
148
+
149
+ ### Timezone verification contract
150
+
151
+ The integration checks all six periods and four object types against real Redis,
152
+ with `Time.zone` set to `Asia/Tokyo` and unset. Timecop freezes UTC at year/month
153
+ and Sunday/Monday boundaries, while the process timezone stays UTC. Weekly keys
154
+ retain the existing Monday-based `%Y`/`%W` format, including the previous year
155
+ when the Monday belongs to December; they are not ISO week-year keys.
156
+
157
+ The contract is that periodical objects select and persist keys using the real
158
+ ActiveSupport `Time.current`/`Time.zone` behavior, with the process clock as the
159
+ fallback when `Time.zone` is unset. The job's `TZ=UTC` fixes the process timezone;
160
+ the [integration spec](spec/integration/redis/recurring_at_intervals_spec.rb)
161
+ uses `Time.use_zone(zone) { example.run }` to set `Asia/Tokyo` or `nil` for each
162
+ example. These settings exercise separate layers of timezone selection.
163
+
164
+ Expected key suffixes are fixed fixture values, independent of `Time.current`
165
+ at assertion time. For example, `2020-12-31 23:30 UTC` is
166
+ `2021-01-01 08:30` in Tokyo, so annual keys must end in `2020` and `2021`,
167
+ respectively. Assertions check the object's key and the corresponding persisted
168
+ Redis entry. Comparing the object's time with `Time.current` alone would not
169
+ establish that a particular timezone was applied.
170
+
171
+ The current inventory is two boundary instants x two zone settings x six periods
172
+ x four object types: **96 examples, with 48 per zone setting**. This covers the
173
+ selected Tokyo/unset cases and year/month/week boundaries; other zones and DST
174
+ transitions remain outside this suite's verified scope.
175
+
176
+ ### Execution inventory guard
177
+
178
+ The [workflow](.github/workflows/ci.yml) also emits an RSpec JSON report and runs
179
+ an [execution inventory guard](.github/scripts/timezone-inventory.cjs). The guard
180
+ requires every expected boundary/zone/period/type combination exactly once, with
181
+ status `passed`. Missing groups or individual combinations, empty selections,
182
+ pending/skipped examples, duplicates, and unexpected examples fail the job.
183
+ The expected dimensions have one definition in the guard; update it alongside
184
+ intentional changes to the integration fixtures. Its regression tests include
185
+ missing Tokyo/unset groups and rejected empty/pending reports. The dated
186
+ record linked below remains evidence for its recorded commit; this check enforces
187
+ the inventory on subsequent runs.
188
+
189
+ Run it separately with the same disposable Compose Redis as the regular suite:
190
+
191
+ ```sh
192
+ docker compose run --rm -e BUNDLE_GEMFILE=gemfiles/active_support/Gemfile ruby bundle install
193
+ docker compose run --rm -e TZ=UTC -e BUNDLE_GEMFILE=gemfiles/active_support/Gemfile ruby bundle exec rspec spec/integration/redis/recurring_at_intervals_spec.rb --exclude-pattern ''
194
+ ```
195
+
196
+ The empty `--exclude-pattern` overrides `.rspec` for this explicit integration
197
+ run. The normal three-Ruby coverage merge remains unchanged; the separate job
198
+ stores its own JUnit and coverage artifacts.
199
+
200
+ The [dated timezone verification record](docs/verification/2026-10-27-timezone.md)
201
+ preserves the earlier run and one-off diagnostic; it is not a new test result.
202
+
203
+ Repository administration and release procedures live in [Maintaining](docs/maintaining.md).
data/Dockerfile CHANGED
@@ -1,7 +1,8 @@
1
- FROM ruby:2.7
1
+ FROM ruby:3.3
2
2
 
3
3
  RUN mkdir /my_app
4
4
  WORKDIR /my_app
5
5
  COPY . /my_app
6
6
 
7
- RUN gem install bundler && bundle
7
+ # Match Gemfile.lock and the supported Ruby baseline.
8
+ RUN gem install bundler -v 4.0.23 --no-document && bundle _4.0.23_ install
data/Gemfile CHANGED
@@ -5,14 +5,14 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in redis-objects-periodical.gemspec
6
6
  gemspec
7
7
 
8
- gem 'bundler', '>= 2.0'
9
- gem 'pry-byebug'
10
- gem 'rake', '~> 13.0'
11
- gem 'rspec', '~> 3.12'
8
+ gem 'bundler', '~> 4.0'
9
+ gem 'pry-byebug', '~> 3.12'
10
+ gem 'rake', '~> 13.4'
11
+ gem 'rspec', '~> 3.13'
12
12
  gem 'rspec_junit_formatter'
13
- gem 'rubocop', '~> 1.36'
13
+ gem 'rubocop', '~> 1.92'
14
14
  gem 'rubocop-performance'
15
15
  gem 'rubocop-rspec'
16
- gem 'simplecov', '0.22.0'
16
+ gem 'simplecov', '1.3.2'
17
17
  gem 'timecop'
18
18
  gem 'yard'
data/Gemfile.lock CHANGED
@@ -1,86 +1,91 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redis-objects-periodical (0.7.0)
4
+ redis-objects-periodical (0.8.0)
5
5
  redis-objects (~> 1.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- ast (2.4.2)
11
- byebug (11.1.3)
10
+ ast (2.4.3)
11
+ byebug (13.0.0)
12
+ reline (>= 0.6.0)
12
13
  coderay (1.1.3)
13
14
  connection_pool (2.3.0)
14
- diff-lcs (1.5.0)
15
- docile (1.4.0)
16
- json (2.6.3)
15
+ diff-lcs (1.6.2)
16
+ io-console (0.9.4)
17
+ json (3.1.0)
18
+ language_server-protocol (3.17.0.6)
19
+ lint_roller (1.1.0)
17
20
  method_source (1.0.0)
18
- parallel (1.22.1)
19
- parser (3.2.1.0)
21
+ parallel (1.28.0)
22
+ parser (3.3.13.0)
20
23
  ast (~> 2.4.1)
21
- pry (0.14.2)
24
+ racc
25
+ prism (1.9.0)
26
+ pry (0.16.1)
22
27
  coderay (~> 1.1)
23
28
  method_source (~> 1.0)
24
- pry-byebug (3.10.1)
25
- byebug (~> 11.0)
26
- pry (>= 0.13, < 0.15)
29
+ reline (>= 0.6.0)
30
+ pry-byebug (3.12.0)
31
+ byebug (~> 13.0)
32
+ pry (>= 0.13, < 0.17)
33
+ racc (1.8.1)
27
34
  rainbow (3.1.1)
28
- rake (13.0.6)
35
+ rake (13.4.2)
29
36
  redis (5.0.6)
30
37
  redis-client (>= 0.9.0)
31
38
  redis-client (0.12.1)
32
39
  connection_pool
33
40
  redis-objects (1.7.0)
34
41
  redis
35
- regexp_parser (2.7.0)
36
- rexml (3.2.5)
37
- rspec (3.12.0)
38
- rspec-core (~> 3.12.0)
39
- rspec-expectations (~> 3.12.0)
40
- rspec-mocks (~> 3.12.0)
41
- rspec-core (3.12.1)
42
- rspec-support (~> 3.12.0)
43
- rspec-expectations (3.12.2)
42
+ regexp_parser (2.13.1)
43
+ reline (0.7.0)
44
+ io-console (~> 0.5)
45
+ rspec (3.13.2)
46
+ rspec-core (~> 3.13.0)
47
+ rspec-expectations (~> 3.13.0)
48
+ rspec-mocks (~> 3.13.0)
49
+ rspec-core (3.13.6)
50
+ rspec-support (~> 3.13.0)
51
+ rspec-expectations (3.13.5)
44
52
  diff-lcs (>= 1.2.0, < 2.0)
45
- rspec-support (~> 3.12.0)
46
- rspec-mocks (3.12.3)
53
+ rspec-support (~> 3.13.0)
54
+ rspec-mocks (3.13.8)
47
55
  diff-lcs (>= 1.2.0, < 2.0)
48
- rspec-support (~> 3.12.0)
49
- rspec-support (3.12.0)
50
- rspec_junit_formatter (0.6.0)
51
- rspec-core (>= 2, < 4, != 2.12.0)
52
- rubocop (1.45.1)
53
- json (~> 2.3)
54
- parallel (~> 1.10)
55
- parser (>= 3.2.0.0)
56
+ rspec-support (~> 3.13.0)
57
+ rspec-support (3.13.7)
58
+ rspec_junit_formatter (0.8.0)
59
+ rspec-core (>= 3.13, < 4)
60
+ rubocop (1.92.1)
61
+ json (>= 2.3)
62
+ language_server-protocol (~> 3.17.0.2)
63
+ lint_roller (~> 1.1.0)
64
+ parallel (>= 1.10)
65
+ parser (>= 3.3.0.2)
56
66
  rainbow (>= 2.2.2, < 4.0)
57
- regexp_parser (>= 1.8, < 3.0)
58
- rexml (>= 3.2.5, < 4.0)
59
- rubocop-ast (>= 1.24.1, < 2.0)
67
+ regexp_parser (>= 2.11.3, < 3.0)
68
+ rubocop-ast (>= 1.49.0, < 2.0)
60
69
  ruby-progressbar (~> 1.7)
61
- unicode-display_width (>= 2.4.0, < 3.0)
62
- rubocop-ast (1.26.0)
63
- parser (>= 3.2.1.0)
64
- rubocop-capybara (2.17.0)
65
- rubocop (~> 1.41)
66
- rubocop-performance (1.16.0)
67
- rubocop (>= 1.7.0, < 2.0)
68
- rubocop-ast (>= 0.4.0)
69
- rubocop-rspec (2.18.1)
70
- rubocop (~> 1.33)
71
- rubocop-capybara (~> 2.17)
72
- ruby-progressbar (1.11.0)
73
- simplecov (0.22.0)
74
- docile (~> 1.1)
75
- simplecov-html (~> 0.11)
76
- simplecov_json_formatter (~> 0.1)
77
- simplecov-html (0.12.3)
78
- simplecov_json_formatter (0.1.4)
79
- timecop (0.9.6)
80
- unicode-display_width (2.4.2)
81
- webrick (1.7.0)
82
- yard (0.9.28)
83
- webrick (~> 1.7.0)
70
+ unicode-display_width (>= 2.4.0, < 4.0)
71
+ rubocop-ast (1.50.0)
72
+ parser (>= 3.3.7.2)
73
+ prism (~> 1.7)
74
+ rubocop-performance (1.27.0)
75
+ lint_roller (~> 1.1)
76
+ rubocop (>= 1.89.0, < 2.0)
77
+ rubocop-ast (>= 1.47.1, < 2.0)
78
+ rubocop-rspec (3.10.2)
79
+ lint_roller (~> 1.1)
80
+ regexp_parser (>= 2.0)
81
+ rubocop (~> 1.86, >= 1.86.2)
82
+ ruby-progressbar (1.13.0)
83
+ simplecov (1.3.2)
84
+ timecop (0.9.11)
85
+ unicode-display_width (3.3.0)
86
+ unicode-emoji (~> 4.3)
87
+ unicode-emoji (4.3.0)
88
+ yard (0.9.45)
84
89
 
85
90
  PLATFORMS
86
91
  aarch64-linux
@@ -89,18 +94,18 @@ PLATFORMS
89
94
  x86_64-linux
90
95
 
91
96
  DEPENDENCIES
92
- bundler (>= 2.0)
93
- pry-byebug
94
- rake (~> 13.0)
97
+ bundler (~> 4.0)
98
+ pry-byebug (~> 3.12)
99
+ rake (~> 13.4)
95
100
  redis-objects-periodical!
96
- rspec (~> 3.12)
101
+ rspec (~> 3.13)
97
102
  rspec_junit_formatter
98
- rubocop (~> 1.36)
103
+ rubocop (~> 1.92)
99
104
  rubocop-performance
100
105
  rubocop-rspec
101
- simplecov (= 0.22.0)
106
+ simplecov (= 1.3.2)
102
107
  timecop
103
108
  yard
104
109
 
105
110
  BUNDLED WITH
106
- 2.3.26
111
+ 4.0.23