jobcompat 0.1.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 +7 -0
- data/CHANGELOG.md +9 -0
- data/LICENSE +21 -0
- data/README.md +161 -0
- data/SECURITY.md +5 -0
- data/docs/architecture.md +587 -0
- data/docs/competitive-analysis.md +268 -0
- data/docs/implementation-plan.md +806 -0
- data/docs/release-checklist-v0.1.0.md +49 -0
- data/docs/release-notes-v0.1.0.md +24 -0
- data/docs/spec-v0.1.md +1157 -0
- data/exe/jobcompat +3 -0
- data/lib/jobcompat/analysis.rb +340 -0
- data/lib/jobcompat/cli.rb +107 -0
- data/lib/jobcompat/config.rb +89 -0
- data/lib/jobcompat/engine.rb +247 -0
- data/lib/jobcompat/errors.rb +12 -0
- data/lib/jobcompat/formatter.rb +64 -0
- data/lib/jobcompat/git_repository.rb +78 -0
- data/lib/jobcompat/version.rb +3 -0
- data/lib/jobcompat.rb +8 -0
- metadata +84 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0c7ba60ac1a983ea795999572de4a8e670986f6b8ed6d5a05433f21a81ca8d03
|
|
4
|
+
data.tar.gz: ba82835508ec2d1e056eeb71a94ec51af5cf5696aa53046b6912c50281640221
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bc94f38e9c059633c5701bc11982298f0d4786b8b58ff11494b0233a6de9f8f5a11e7e3e05ddae563a16773094be3ff410eff881e319cd3b1d8d8a0b336518a1
|
|
7
|
+
data.tar.gz: bb16e4959a69dde9a1aac0fbaff894aa3291009d538927a83a721d7a64bfee67ef804a978d5044ed7c4f7ed8fda66aa09b4a7a8caccf2da1bb1b25a44a5c89c3
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - 2026-09-26
|
|
4
|
+
|
|
5
|
+
- Extract positional `perform` contracts from native `Sidekiq::Job` and `Sidekiq::Worker` classes, including reopened classes.
|
|
6
|
+
- Discover direct Sidekiq enqueue calls and compare Git base and head snapshots in all three rolling deployment directions.
|
|
7
|
+
- Report JC001–JC007 in text or JSON schema version 1, with targeted `.jobcompat.yml` suppression.
|
|
8
|
+
- Check tracked Ruby declarations through a separate `DefinedConstantIndex` before reporting class removal.
|
|
9
|
+
- Provide a Prism based static CLI and RubyGem for Ruby 3.3 or newer without booting Rails or connecting to Redis.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jobcompat contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# jobcompat
|
|
2
|
+
|
|
3
|
+
Catch Sidekiq job argument changes that break queued jobs before deploy.
|
|
4
|
+
|
|
5
|
+
```diff
|
|
6
|
+
-def perform(user_id)
|
|
7
|
+
+def perform(user_id, format)
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
ERROR JC001 ExportJob
|
|
12
|
+
Base emits 1 argument, but the HEAD worker accepts 2.
|
|
13
|
+
Risk: Jobs queued by the base revision may fail after deployment.
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
No schemas. No annotations. No committed snapshots. No Redis connection. No Rails boot. jobcompat compares existing Ruby source across Git commits.
|
|
17
|
+
|
|
18
|
+
Your API has a schema. Your database has migrations. What protects your queued jobs?
|
|
19
|
+
|
|
20
|
+
## Problem
|
|
21
|
+
|
|
22
|
+
A Sidekiq producer can enqueue arguments today that a different worker version executes later. Changing `perform` can break jobs retained in queues, retry sets, or scheduled sets, and a rolling deploy can run old and new processes together.
|
|
23
|
+
|
|
24
|
+
## Why this happens
|
|
25
|
+
|
|
26
|
+
Sidekiq persists a worker `class` and an `args` array. The worker receives those positional arguments through `perform(*args)`. A source change to the method's accepted arity can make a previously valid array invalid.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
Ruby 3.3 or newer is required. After release, install with `gem install jobcompat`, or add `gem "jobcompat", require: false` to your Gemfile, run `bundle install`, and use `bundle exec jobcompat`. `prism` is the only runtime dependency; jobcompat does not require the Sidekiq gem to inspect source.
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
From a Git repository containing native Sidekiq jobs:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
jobcompat check --base origin/main
|
|
38
|
+
jobcompat check --base v0.1.0 --head HEAD --format json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Both refs resolve to commits. Staged and uncommitted changes are ignored. The command never checks out another revision or changes the working tree.
|
|
42
|
+
|
|
43
|
+
## Example output
|
|
44
|
+
|
|
45
|
+
For a base one-argument producer and a HEAD worker that requires two arguments, text output includes:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
ERROR JC001 ExportJob
|
|
49
|
+
Base emits 1 argument, but the HEAD worker accepts 2.
|
|
50
|
+
Revisions: base, head
|
|
51
|
+
Affected directions:
|
|
52
|
+
base producer -> HEAD consumer
|
|
53
|
+
HEAD producer -> HEAD consumer
|
|
54
|
+
Risk: Jobs queued by the base revision may fail after deployment.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The second direction appears only when HEAD also has a one-argument producer. Output includes source locations, suggested migration steps, resolved commit SHAs, and a summary. JSON output uses `schema_version: 1` and includes per-worker compatibility matrices.
|
|
58
|
+
|
|
59
|
+
## How it works
|
|
60
|
+
|
|
61
|
+
jobcompat reads committed Ruby blobs through Git, parses each selected file with Prism, groups reopened class fragments by canonical name, discovers direct Sidekiq includes and enqueue calls, then compares positional-arity intervals. Application code is never loaded or executed.
|
|
62
|
+
|
|
63
|
+
Its separate `DefinedConstantIndex` checks tracked Ruby class declarations and named bindings before declaring a worker class absent. A class that remains in an excluded file or stops using a direct Sidekiq include produces a warning rather than a removal error.
|
|
64
|
+
|
|
65
|
+
## Compatibility directions
|
|
66
|
+
|
|
67
|
+
| Direction | Question |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `base_to_head` | Can the new worker execute a payload produced by the base revision? |
|
|
70
|
+
| `head_to_base` | Can an old worker execute a payload enqueued by a new application node? |
|
|
71
|
+
| `head_to_head` | Does HEAD's own producer match its worker? |
|
|
72
|
+
|
|
73
|
+
The model assumes rolling deployment with old and new processes potentially overlapping. A discovered callsite is treated as potentially reachable; jobcompat does not evaluate feature flags or deployment sequencing.
|
|
74
|
+
|
|
75
|
+
## Rules
|
|
76
|
+
|
|
77
|
+
| Rule | Severity | Meaning |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| JC001 | ERROR | Base payload accepted by base worker is rejected by HEAD worker |
|
|
80
|
+
| JC002 | ERROR | HEAD payload accepted by HEAD worker is rejected by base worker |
|
|
81
|
+
| JC003 | ERROR | HEAD producer and HEAD worker disagree |
|
|
82
|
+
| JC004 | ERROR | Base worker class is proven absent from HEAD tracked Ruby source |
|
|
83
|
+
| JC005 | ERROR | HEAD enqueues a new worker whose class is proven absent from base source |
|
|
84
|
+
| JC006 | WARNING | Contract narrowed without a qualifying producer witness |
|
|
85
|
+
| JC007 | WARNING | Visible evidence is unsupported or cannot prove compatibility |
|
|
86
|
+
|
|
87
|
+
One JC001 can cover both `base_to_head` and `head_to_head`; the same mismatch is not repeated as JC003. The same unsupported source evidence in both revisions is one JC007 finding.
|
|
88
|
+
|
|
89
|
+
## Configuration and targeted suppression
|
|
90
|
+
|
|
91
|
+
Create `.jobcompat.yml` at the Git root when defaults are insufficient:
|
|
92
|
+
|
|
93
|
+
```yaml
|
|
94
|
+
version: 1
|
|
95
|
+
scan:
|
|
96
|
+
include:
|
|
97
|
+
- "**/*.rb"
|
|
98
|
+
exclude:
|
|
99
|
+
- "vendor/**"
|
|
100
|
+
- "tmp/**"
|
|
101
|
+
- "log/**"
|
|
102
|
+
- "coverage/**"
|
|
103
|
+
- ".bundle/**"
|
|
104
|
+
- "test/**"
|
|
105
|
+
- "spec/**"
|
|
106
|
+
- "features/**"
|
|
107
|
+
- "examples/**"
|
|
108
|
+
ignore:
|
|
109
|
+
- rule: JC005
|
|
110
|
+
worker: ExperimentalJob
|
|
111
|
+
reason: "Enqueue starts after deployment completes"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Each configured `include` or `exclude` list **replaces** its default list, so copy the defaults you still want. `--config PATH` selects another YAML file; a relative path is resolved from the invocation directory. Unknown keys and unsafe YAML aliases fail with exit 2. An ignore entry must have an exact rule, canonical worker name, and non-blank reason. Matched suppressions appear in text and JSON audit output.
|
|
115
|
+
|
|
116
|
+
## CI usage
|
|
117
|
+
|
|
118
|
+
Run `jobcompat check --base origin/main --format json` after fetching the comparison ref in CI. Exit 0 means no unsuppressed compatibility errors (warnings may exist), exit 1 means one or more errors, and exit 2 means the analysis could not complete because of CLI, config, Git, parse, or tool failure. A warning never becomes a pass claim for the affected evidence.
|
|
119
|
+
|
|
120
|
+
## Supported Sidekiq patterns
|
|
121
|
+
|
|
122
|
+
Direct `include Sidekiq::Job` and legacy `include Sidekiq::Worker` are recognized in top-level and supported namespaced classes. Reopened fragments may split the include and `perform` across files. The producer forms are `Job.perform_async(...)`, `Job.perform_in(schedule, ...)`, `Job.perform_at(time, ...)`, and `Job.set(...).perform_async(...)`. Hash and Array expressions each count as one payload argument. A splat or forwarded producer argument makes arity unknown.
|
|
123
|
+
|
|
124
|
+
Positional `perform` parameters may be required, optional, rest, post-rest, or forwarding. Keyword parameters make the worker contract unknown for v0.1.
|
|
125
|
+
|
|
126
|
+
## Limitations and v0.1 non-goals
|
|
127
|
+
|
|
128
|
+
v0.1 checks **positional arity only**. It does not check value types, Hash internals, keyword compatibility, or JSON serialization. It supports native Sidekiq, not ActiveJob. Only direct includes and the documented direct producer calls are analyzed. `Sidekiq::Client.push`, bulk APIs, wrappers, aliases, inheritance, concerns, arbitrary metaprogramming, feature flags, and general Ruby class/module or superclass conflict analysis are outside scope.
|
|
129
|
+
|
|
130
|
+
No repository producer callsite does **not** prove an empty queue: queued, scheduled, retried, historical, and external jobs may exist. JC004 requires a completed static absence proof across tracked `.rb` source. An excluded or unrecognized declaration warns. JC005 is a structural rolling-deploy error conditional on an old Sidekiq process being able to consume the new job's queue. Queue isolation and feature flags are not analyzed. Dynamic source may warn or be missed. Working-tree edits are ignored.
|
|
131
|
+
|
|
132
|
+
## Why not just tests, Sorbet, or Sidekiq strict arguments?
|
|
133
|
+
|
|
134
|
+
Tests usually run one revision at a time. Sorbet and Sidekiq's strict argument checking address different contracts, such as types or JSON-safe values. jobcompat checks source-derived positional arity across committed revisions and rolling deployment directions. It complements those tools.
|
|
135
|
+
|
|
136
|
+
## Deployment assumptions and staged migration
|
|
137
|
+
|
|
138
|
+
For a new argument, first deploy a worker that accepts it optionally, without producing it:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
def perform(user_id, format = nil)
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
After old workers have left the fleet, begin enqueueing `format`. Make the argument required only after jobs with the old shape can no longer be retained or retried. If an external rollout gate makes a reported risk impossible, use a targeted suppression with its reason.
|
|
146
|
+
|
|
147
|
+
## Security and offline-friendly characteristics
|
|
148
|
+
|
|
149
|
+
Analysis reads local Git objects only. It does not connect to Redis, boot Rails, load application code, invoke Git hooks, or upload source. Git commands use argv arguments and resolved immutable commit SHAs. It needs no network once Ruby dependencies and Git objects are available.
|
|
150
|
+
|
|
151
|
+
## Roadmap
|
|
152
|
+
|
|
153
|
+
Possible future work includes additional native Sidekiq producer APIs and SARIF output. Other frameworks would be considered only after v0.1 usage evidence; they are not supported now.
|
|
154
|
+
|
|
155
|
+
## Contributing
|
|
156
|
+
|
|
157
|
+
Run `bundle exec rake test` and `gem build jobcompat.gemspec` with Ruby 3.3 or newer. Tests create temporary Git repositories and need no Redis or Sidekiq server. See [the normative v0.1 spec](docs/spec-v0.1.md) before changing rule behavior.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT. See [LICENSE](LICENSE).
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
If you find a vulnerability, use this repository's GitHub private vulnerability reporting. Please avoid posting an exploitable report in a public issue.
|
|
4
|
+
|
|
5
|
+
Supported release: 0.1.x after publication. Security fixes will be released as a new gem version rather than replacing an existing one.
|