jobcompat 0.2.1 → 0.3.1
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 +12 -0
- data/README.md +34 -4
- data/SECURITY.md +5 -1
- data/docs/architecture.md +45 -5
- data/docs/implementation-plan-v0.3.1.md +54 -0
- data/docs/implementation-plan-v0.3.md +32 -0
- data/docs/release-checklist-v0.3.0.md +109 -0
- data/docs/release-notes-v0.3.0.md +115 -0
- data/docs/spec-v0.3.1.md +202 -0
- data/docs/spec-v0.3.md +238 -0
- data/lib/jobcompat/aliases.rb +86 -0
- data/lib/jobcompat/analysis.rb +76 -22
- data/lib/jobcompat/cli.rb +12 -3
- data/lib/jobcompat/engine.rb +170 -63
- data/lib/jobcompat/explanation.rb +144 -0
- data/lib/jobcompat/formatter.rb +11 -1
- data/lib/jobcompat/version.rb +1 -1
- data/lib/jobcompat.rb +1 -0
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe462bfac086361d10bc4be2bb535045c131d0cbabb9037b437789684665eb29
|
|
4
|
+
data.tar.gz: ecc7ee3319c763957771f3eff973ae3f7ace1b5deb43fda27de694b9fea8531f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 137621193e3cbbad8cc1c368718390ee4c65e7c0ed37a5ba2a2ae798b24d4fbd0aa05ba26673e26b968c44bb89cc36b6cfc2146bc11638eb2ead7e97d19c31a1
|
|
7
|
+
data.tar.gz: e91710462947c4376ccc8fcc3f3f167fc7dfd2e21e1fa9d61d0197d69538f4707aec557fb662c6a5ec2785bb4688744866545bbcd395734afb9dc3a97a15a55b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1 - 2026-11-07
|
|
4
|
+
|
|
5
|
+
- Add optional `--explain` text output showing the evidence, decision, and boundary behind each surviving JC001–JC007 finding, including alias provenance for serialized identities.
|
|
6
|
+
- Compatibility decisions, default text output, and JSON schema 3 content are unchanged; combining `--explain` with `--format json` fails with a config error before analysis.
|
|
7
|
+
|
|
8
|
+
## 0.3.0 - 2026-10-05
|
|
9
|
+
|
|
10
|
+
- Recognize static Sidekiq compatibility aliases, including finite chains and supported namespaces, to preserve old serialized identities across worker renames.
|
|
11
|
+
- Normalize Class-object producers through aliases to the terminal Class name while preserving exact Client String identities.
|
|
12
|
+
- Report relevant unresolved, cyclic, conflicting, or unsupported alias bindings conservatively as JC007.
|
|
13
|
+
- Emit JSON schema 3 with effective serialized-identity worker results, alias metadata, and resolved_alias presence; direct worker summary counts and config schema 1 remain unchanged.
|
|
14
|
+
|
|
3
15
|
## 0.2.1 - 2026-10-04
|
|
4
16
|
|
|
5
17
|
Canonical worker-name consistency:
|
data/README.md
CHANGED
|
@@ -36,10 +36,13 @@ From a Git repository containing native Sidekiq jobs:
|
|
|
36
36
|
```sh
|
|
37
37
|
jobcompat check --base origin/main
|
|
38
38
|
jobcompat check --base v0.1.0 --head HEAD --format json
|
|
39
|
+
jobcompat check --base origin/main --explain
|
|
39
40
|
```
|
|
40
41
|
|
|
41
42
|
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
|
|
|
44
|
+
`--explain` appends an `Explanation` section—evidence, decision, and boundary—to each remaining finding in text output. It shows why a rule fired from the static evidence jobcompat already collected: it runs no additional compatibility checks, cannot be combined with `--format json` (exit 2 config error), reports only static source evidence, and never turns an `unknown` result into a safety claim.
|
|
45
|
+
|
|
43
46
|
## Example output
|
|
44
47
|
|
|
45
48
|
For a base one-argument producer and a HEAD worker that requires two arguments, text output includes:
|
|
@@ -54,7 +57,7 @@ ERROR JC001 ExportJob
|
|
|
54
57
|
Risk: Jobs queued by the base revision may fail after deployment.
|
|
55
58
|
```
|
|
56
59
|
|
|
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:
|
|
60
|
+
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: 3` and includes per-serialized-identity compatibility matrices and alias provenance.
|
|
58
61
|
|
|
59
62
|
## How it works
|
|
60
63
|
|
|
@@ -140,9 +143,36 @@ jobcompat sees payload arities 1 and 2. A dynamic bulk row is retained as `dynam
|
|
|
140
143
|
|
|
141
144
|
Positional `perform` parameters may be required, optional, rest, post-rest, or forwarding. Keyword parameters make the worker contract unknown in v0.2.
|
|
142
145
|
|
|
143
|
-
|
|
146
|
+
### Worker rename and static compatibility aliases
|
|
147
|
+
|
|
148
|
+
jobcompat recognizes plain static constant aliases to directly recognized jobs, including finite chains and supported namespaces:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
class MyNewJob
|
|
152
|
+
include Sidekiq::Job
|
|
153
|
+
def perform(id); end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
MyOldJob = MyNewJob
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Old queued `"MyOldJob"` payloads can resolve through the alias to the new worker contract, so this rename does not produce JC004. Arity incompatibilities still produce the normal findings under the persisted name.
|
|
160
|
+
|
|
161
|
+
`MyOldJob.perform_async(1)` uses the aliased Class object: Sidekiq serializes its canonical name **MyNewJob**, not the source alias token. This also applies to scheduling, bulk, Setter calls, and Client constant payloads. During a rolling deployment, activating that producer can still cause JC005 MyNewJob if old processes cannot resolve the new identity. A compatibility alias alone does not make immediate new-name producer activation safe.
|
|
162
|
+
|
|
163
|
+
By contrast, an explicit Client String keeps its exact identity:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
Sidekiq::Client.push("class" => "MyOldJob", "args" => [1])
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
jobcompat models that producer as MyOldJob, then follows the alias on the consumer side. This describes identity behavior; Client Strings also have different job-option/default semantics.
|
|
170
|
+
|
|
171
|
+
Only unconditional plain constant assignments are trusted. Cycles, conflicting or repeated bindings, dynamic assignments, and conditional aliases warn when relevant. Excluded aliases can block an absence proof but are not trusted as compatibility paths. There is no const_set/autoload/const_missing support, conditional alias proof, runtime load-order proof, or heuristic rename detection. Recognition assumes successful application boot, just as direct worker recognition does.
|
|
172
|
+
|
|
173
|
+
## Limitations and non-goals
|
|
144
174
|
|
|
145
|
-
|
|
175
|
+
jobcompat checks **positional arity only**. It does not check value types, Hash internals, keyword compatibility, or JSON serialization. It supports native Sidekiq, not ActiveJob. Dynamic bulk collections are warning/unknown, Client payload variables are not followed, and custom wrappers are not analyzed. ActiveJob, arbitrary Client instances, Hash internal schemas, general Ruby aliases beyond the static subset above, inheritance, concerns, arbitrary metaprogramming, feature flags, and general Ruby class/module or superclass conflict analysis remain outside scope.
|
|
146
176
|
|
|
147
177
|
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.
|
|
148
178
|
|
|
@@ -171,7 +201,7 @@ Possible future work includes SARIF output and additional evidence sources. Othe
|
|
|
171
201
|
|
|
172
202
|
## Contributing
|
|
173
203
|
|
|
174
|
-
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. The [v0.1 specification](docs/spec-v0.1.md) remains the baseline; [the normative v0.2 delta](docs/spec-v0.2.md) defines the added producer coverage,
|
|
204
|
+
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. The [v0.1 specification](docs/spec-v0.1.md) remains the baseline; [the normative v0.2 delta](docs/spec-v0.2.md) defines the added producer coverage, [the v0.2.1 delta](docs/spec-v0.2.1.md) defines shared canonical worker-name validation, and [the v0.3 delta](docs/spec-v0.3.md) defines static compatibility aliases and serialized identity resolution.
|
|
175
205
|
|
|
176
206
|
## License
|
|
177
207
|
|
data/SECURITY.md
CHANGED
|
@@ -2,4 +2,8 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
-
Supported
|
|
5
|
+
## Supported releases
|
|
6
|
+
|
|
7
|
+
The latest published minor release series is supported for security fixes. Older versions may receive fixes at the maintainer's discretion.
|
|
8
|
+
|
|
9
|
+
Security fixes are published as new gem versions. Existing published gem versions are not replaced.
|
data/docs/architecture.md
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
# jobcompat v0.
|
|
1
|
+
# jobcompat v0.3.0 architecture
|
|
2
2
|
|
|
3
|
-
Status: implemented design for `0.
|
|
4
|
-
Date: 2026-
|
|
3
|
+
Status: implemented design for `0.3.0`
|
|
4
|
+
Date: 2026-10-05
|
|
5
|
+
|
|
6
|
+
This document describes the current v0.3.0 architecture, extending the
|
|
7
|
+
historical v0.1 and v0.2 design. References to those releases record inherited
|
|
8
|
+
design decisions and additions. The current JSON output schema is 3;
|
|
9
|
+
schema 2 describes historical v0.2 behavior. `docs/spec-v0.3.md` defines the
|
|
10
|
+
normative v0.3.0 contract.
|
|
5
11
|
|
|
6
12
|
## 1. Architecture principles
|
|
7
13
|
|
|
@@ -214,7 +220,7 @@ The engine MUST be pure with respect to filesystem, Git, environment, clock, and
|
|
|
214
220
|
Formatters receive a completed or failed result envelope. They MUST NOT recalculate compatibility or suppression. They render already matched suppression audit records without reconstructing omitted findings.
|
|
215
221
|
|
|
216
222
|
- `Formatter::Text` renders concise human remediation.
|
|
217
|
-
- `Formatter::Json` constructs schema-version-
|
|
223
|
+
- `Formatter::Json` constructs schema-version-3 primitive Hash/Array data, retaining the historical v0.2 fields and adding alias metadata and `resolved_alias` presence. It includes each finding's `revisions`, `directions`, and `unknown_reason` and each worker's base/head presence status, calls `JSON.pretty_generate`, and appends exactly one newline.
|
|
218
224
|
|
|
219
225
|
Both consume already sorted results.
|
|
220
226
|
|
|
@@ -364,12 +370,46 @@ UnresolvedEnqueueCall
|
|
|
364
370
|
unknown_reason Symbol | nil
|
|
365
371
|
```
|
|
366
372
|
|
|
367
|
-
The reason values
|
|
373
|
+
The reason values retain the frozen baseline strings in `docs/spec-v0.1.md` and the historical v0.2/schema-v2 producer additions in `docs/spec-v0.2.md`, with current alias additions defined in `docs/spec-v0.3.md`; visitors do not invent free-form reason text.
|
|
368
374
|
|
|
369
375
|
Only after both revisions' worker names are known does the engine resolve these facts. This avoids ordering dependence between files and enables head calls to removed base workers.
|
|
370
376
|
|
|
371
377
|
## 7. Internal models
|
|
372
378
|
|
|
379
|
+
### v0.3.0 serialized identity resolution
|
|
380
|
+
|
|
381
|
+
The existing Analyzer Prism pass extracts direct worker facts together with
|
|
382
|
+
selected-source ConstantBinding and AliasCandidate facts. AliasResolver
|
|
383
|
+
builds a deterministic per-revision graph using the same constant-path
|
|
384
|
+
and lexical namespace rules, with iterative cycle detection and strict
|
|
385
|
+
binding uniqueness. DefinedConstantIndex remains separate presence proof;
|
|
386
|
+
its excluded-file facts never become trusted alias edges.
|
|
387
|
+
Selected ambiguous paths retain separate leaf-name binding blockers; the
|
|
388
|
+
resolver checks them at alias edges and direct terminals without evaluating
|
|
389
|
+
dynamic receivers. Unrecognized terminal declarations remain diagnostic
|
|
390
|
+
proof locations.
|
|
391
|
+
|
|
392
|
+
RevisionSnapshot retains direct `workers` for summary counts and adds
|
|
393
|
+
`aliases`, effective consumer contracts by serialized identity, and presence
|
|
394
|
+
helpers. A resolved alias carries terminal contract and every edge's proof
|
|
395
|
+
locations; an unknown binding masks a conflicting direct consumer.
|
|
396
|
+
The snapshot also retains selected binding facts so alias-related producer
|
|
397
|
+
lookup stops at nearer non-worker class/module declarations.
|
|
398
|
+
|
|
399
|
+
Engine normalizes Class-object calls through their own revision's alias
|
|
400
|
+
graph to terminal identities, preserving known identity even with unknown
|
|
401
|
+
arity. Exact Client Strings retain their identity. Diagnostic attribution
|
|
402
|
+
is separate from known serialized producer identity, so an unknown alias
|
|
403
|
+
cannot drive JC005.
|
|
404
|
+
Class-object alias calls carry terminal declaration/include/perform proof.
|
|
405
|
+
Opposite revision names cannot override local bindings during alias lookup;
|
|
406
|
+
the existing non-alias cross-revision attribution is preserved.
|
|
407
|
+
The bounded comparison set includes historical BASE aliases and relevant
|
|
408
|
+
producer/binding identities; dormant HEAD aliases do
|
|
409
|
+
not automatically add warnings. Existing rules consume effective contracts.
|
|
410
|
+
Schema 3 exposes base_alias/head_alias and resolved_alias presence while
|
|
411
|
+
summary.workers still counts direct worker facts.
|
|
412
|
+
|
|
373
413
|
Models SHOULD be immutable `Data.define` values on Ruby 3.3. Constructors validate invariants at the boundary; rule methods do not repeatedly revalidate them.
|
|
374
414
|
|
|
375
415
|
### 7.1 `SourceLocation`
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# jobcompat v0.3.1 implementation plan
|
|
2
|
+
|
|
3
|
+
Baseline: `02751a57e21da3f548f6d6442b17a438026ef9e1` (v0.3.0). All changes
|
|
4
|
+
stay uncommitted for independent review.
|
|
5
|
+
|
|
6
|
+
## Change order
|
|
7
|
+
|
|
8
|
+
1. `lib/jobcompat/explanation.rb` (new): `Explanation = Data.define(:evidence, :decision, :boundary)` plus a private builder module used by the Engine. Evidence entries, decision, and boundary are plain frozen strings; rendering responsibility stays with the Formatter.
|
|
9
|
+
2. `lib/jobcompat/engine.rb`: `evaluate(with_explanations: false)`. At each
|
|
10
|
+
finding site (JC001, JC002, JC003, JC004, JC005, JC006, presence-unknown
|
|
11
|
+
JC007, and the aggregated JC007 roots) build an `Explanation` from the
|
|
12
|
+
producer group, contracts, presence result, alias resolution, and unknown
|
|
13
|
+
root already in scope, and store it in a
|
|
14
|
+
`{}.compare_by_identity` sidecar keyed by the finding hash object. The
|
|
15
|
+
sidecar is returned as `explanations:` only when requested. Finding and
|
|
16
|
+
worker rows are not modified.
|
|
17
|
+
3. `lib/jobcompat/formatter.rb`: `text(envelope, explanations: nil)`. When a
|
|
18
|
+
non-nil sidecar maps the finding, render the fixed `Explanation:` /
|
|
19
|
+
`Evidence:` / `Decision:` / `Boundary:` block between the location lines
|
|
20
|
+
and `Suggested migration:`. `explanations: nil` keeps output
|
|
21
|
+
byte-identical. `json` untouched.
|
|
22
|
+
4. `lib/jobcompat/cli.rb`: parse `--explain` (default false); reject
|
|
23
|
+
`--explain` with non-text format as `config_error` before repository
|
|
24
|
+
access; pass `with_explanations:` to `evaluate` and `explanations:` to
|
|
25
|
+
`Formatter.text` for the completed text path only.
|
|
26
|
+
5. `lib/jobcompat/version.rb` -> 0.3.1; `Gemfile.lock` path spec version.
|
|
27
|
+
6. `docs/spec-v0.3.1.md` (normative delta), this plan, README quick-start and
|
|
28
|
+
explanation notes, `CHANGELOG.md` `## 0.3.1 - Unreleased`.
|
|
29
|
+
|
|
30
|
+
## Test order
|
|
31
|
+
|
|
32
|
+
1. Unit `test/unit/explanation_test.rb`: Explanation VO shape; formatter
|
|
33
|
+
parity (nil sidecar byte-identical, unknown finding -> no section);
|
|
34
|
+
sanitization of hostile strings.
|
|
35
|
+
2. Integration `test/integration/explain_command_test.rb`: CLI contract
|
|
36
|
+
(accept/combos/order/json rejection/failed envelope schema 3/help);
|
|
37
|
+
per-rule content for JC001 (both direction variants, alias-carried
|
|
38
|
+
identity), JC002, JC003 (no base leakage), JC004 (absence only;
|
|
39
|
+
outside-scope fixture must not claim absence), JC005 (conditional
|
|
40
|
+
rolling-deploy wording, Class-object terminal identity, exact Client
|
|
41
|
+
String identity), JC006 (no empty-queue claim), JC007 (alias cycle,
|
|
42
|
+
unresolved, splat producer, keyword contract); suppression behavior;
|
|
43
|
+
zero-finding behavior; determinism double-run; default-output regression
|
|
44
|
+
against committed expectations derived from the frozen 0.3.0 baselines.
|
|
45
|
+
3. Full suite `rake test`; baseline runner `compare` (byte-identical after
|
|
46
|
+
version normalization only).
|
|
47
|
+
4. `gem build`, package content inspection, isolated install, CLI smoke.
|
|
48
|
+
|
|
49
|
+
## Regression evidence
|
|
50
|
+
|
|
51
|
+
Frozen 0.3.0 outputs for JC001–JC007 (including alias-carried, client-string,
|
|
52
|
+
and outside-scope variants), resolved alias rename, targeted suppression, and
|
|
53
|
+
a no-findings comparison were captured before implementation and are
|
|
54
|
+
re-compared after; only the version string may differ.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# v0.3.0 implementation plan
|
|
2
|
+
|
|
3
|
+
Implement the normative delta in spec-v0.3.md in this order.
|
|
4
|
+
|
|
5
|
+
1. Extend Analyzer's existing Prism pass with immutable AliasCandidate and
|
|
6
|
+
ConstantBinding facts. Propagate a narrow direct-statement context for
|
|
7
|
+
alias proof, while retaining baseline worker/producer discovery. Keep
|
|
8
|
+
DefinedConstantIndex responsible only for conservative presence.
|
|
9
|
+
2. Build AliasResolver from selected binding inventory and direct workers.
|
|
10
|
+
Reuse one constant candidate helper. Iterate each chain with cycle
|
|
11
|
+
detection; reject duplicate/conflicting bindings and unsupported edges.
|
|
12
|
+
Retain deterministic chain, locations, and revision-free fingerprints.
|
|
13
|
+
3. Extend RevisionSnapshot with aliases and effective consumer/presence
|
|
14
|
+
helpers. Preserve workers as direct facts and summary counts. Derive
|
|
15
|
+
alias contracts from terminal contracts with complete proof locations.
|
|
16
|
+
4. Resolve producers using snapshot-local alias semantics. Keep identity
|
|
17
|
+
certainty separate from arity certainty and diagnostic attribution.
|
|
18
|
+
Preserve exact Client Strings and baseline non-alias attribution.
|
|
19
|
+
5. Feed relevant serialized identities and effective consumers into Engine.
|
|
20
|
+
Keep JC001-JC007 logic and precedence; add alias warnings/metadata and
|
|
21
|
+
update identity wording. Emit schema 3 on completed and failed CLI output.
|
|
22
|
+
6. Add focused AST/graph/producer unit tests and temporary Git rule,
|
|
23
|
+
deployment, JSON, determinism, Unicode, suppression, and security tests.
|
|
24
|
+
Update only intentional version/schema expectations in existing tests.
|
|
25
|
+
7. Update README, architecture, changelog, version and lockfile. Run all
|
|
26
|
+
tests in available Ruby with UTF-8 locale, build and inspect the gem,
|
|
27
|
+
install into a temporary isolated destination, run CLI exit scenarios,
|
|
28
|
+
inspect the full diff, and remove only the generated package.
|
|
29
|
+
|
|
30
|
+
Mandala tracks specification, aliases, producers, compatibility, validation.
|
|
31
|
+
Only evidence-backed completed facets are marked done. No release operation
|
|
32
|
+
is part of this plan.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# jobcompat v0.3.0 release checklist
|
|
2
|
+
|
|
3
|
+
This document prepares jobcompat v0.3.0 for release. Checked boxes record completed preparation evidence. Unchecked boxes are final tag, publication, GitHub Release, or post-publication gates. This preparation does not authorize creating or pushing `v0.3.0`.
|
|
4
|
+
|
|
5
|
+
**Pushing `v0.3.0` triggers the Release workflow and may publish the gem to RubyGems. Treat tag push as a publication action.**
|
|
6
|
+
|
|
7
|
+
## Release identity
|
|
8
|
+
|
|
9
|
+
- Repository: `cottondesu/jobcompat`, branch `main`.
|
|
10
|
+
- Gem and CLI version: `0.3.0`; intended annotated tag: `v0.3.0`.
|
|
11
|
+
- Intended release date: `2026-10-05`. If execution is delayed and publication is no longer intended for this date, update the release date consistently before committing or tagging; never backdate publication.
|
|
12
|
+
- JSON schema: `3`; `.jobcompat.yml` configuration schema: `1`.
|
|
13
|
+
- Ruby: `>= 3.3`; Prism: `>= 1.9, < 2`; no new runtime dependencies.
|
|
14
|
+
- Implementation commit: `642e2c4c838db78d4e7ae6dd9ff3b958b6336483` (`Add worker rename alias compatibility`), parent `6f6c2deecad10111d70455dd343e1f89a3af088c`.
|
|
15
|
+
- Implementation CI: run `37302967087`.
|
|
16
|
+
- Release candidate SHA: determined by this preparation commit; record the exact SHA and its exact CI run in the final preparation report. The release-preparation commit, not the implementation commit, must become the eventual tag target if its exact CI passes.
|
|
17
|
+
|
|
18
|
+
## Reviewed implementation
|
|
19
|
+
|
|
20
|
+
- [x] Fresh independent review: SHIP; 0 BLOCKER and 0 MAJOR.
|
|
21
|
+
- [x] Final checked-in suite baseline: 189 tests, 1,942 assertions, 0 failures, 0 errors, 0 skips.
|
|
22
|
+
- [x] Reviewer documentation MINOR resolved before the implementation commit.
|
|
23
|
+
- [x] Analysis does not execute source, boot Rails or Sidekiq, or connect to Redis.
|
|
24
|
+
|
|
25
|
+
## Implementation CI evidence
|
|
26
|
+
|
|
27
|
+
Implementation commit `642e2c4c838db78d4e7ae6dd9ff3b958b6336483` passed GitHub Actions run `37302967087`:
|
|
28
|
+
|
|
29
|
+
- [x] Ruby 3.3 tests and gem build succeeded.
|
|
30
|
+
- [x] Ruby 3.4 tests and gem build succeeded.
|
|
31
|
+
- [x] Ruby 4.0 tests and gem build succeeded.
|
|
32
|
+
|
|
33
|
+
This implementation CI does not replace the required official CI on the release-preparation commit.
|
|
34
|
+
|
|
35
|
+
## Release-only local validation
|
|
36
|
+
|
|
37
|
+
- [x] `LANG=C.UTF-8 bundle exec rake test` under Ruby 3.4.8 / Prism 1.9.0: 189 tests, 1,942 assertions, 0 failures, 0 errors, 0 skips.
|
|
38
|
+
- [x] `git diff --check` passes.
|
|
39
|
+
- [x] `gem build jobcompat.gemspec` succeeds; package inspected and generated `.gem` removed after inspection.
|
|
40
|
+
- [x] Both v0.3.0 release documents are packaged under the existing `docs/*.md` gemspec policy; package contains no generated or local tooling files.
|
|
41
|
+
- [x] Installed the candidate gem into a fresh isolated destination; `jobcompat --version` prints `jobcompat 0.3.0`.
|
|
42
|
+
- [x] The full suite and alias integration scenarios verify safe alias 0, JC001 1, JC005 1, warning-only alias uncertainty 0, invalid config 2, and invalid ref 2.
|
|
43
|
+
- [x] Alias integration scenarios verify safe alias resolution, Class-object producer identity, exact Client String identity, and schema 3 fields; product tests were not changed.
|
|
44
|
+
|
|
45
|
+
## Publication prerequisites
|
|
46
|
+
|
|
47
|
+
- [ ] Manually confirm GitHub environment `release` exists, its required reviewer/protection is intact, and its tag deployment rule remains acceptable. Do not weaken the environment.
|
|
48
|
+
- [ ] Manually confirm the RubyGems Trusted Publisher for `jobcompat` matches owner `cottondesu`, repository `jobcompat`, workflow `release.yml`, and environment `release`. Do not introduce a long-lived RubyGems API key.
|
|
49
|
+
|
|
50
|
+
## Public-state precheck (2026-10-05)
|
|
51
|
+
|
|
52
|
+
- [x] RubyGems lists `0.2.1`, `0.2.0`, and `0.1.0`; `0.3.0` is absent.
|
|
53
|
+
- [x] The local `v0.3.0` tag is absent; remote tags include `v0.2.1` and no `v0.3.0`.
|
|
54
|
+
- [x] GitHub Releases include `v0.2.1`, `v0.2.0`, and `v0.1.0`; `v0.3.0` is absent.
|
|
55
|
+
- [x] Historical `v0.2.1` annotated tag object is `b9ca1f7a3d0818ee23beff48907cb3d6a2e0eb1c`, peeled commit is `6f6c2deecad10111d70455dd343e1f89a3af088c`, and the GitHub Release and RubyGems version are present.
|
|
56
|
+
- [ ] Immediately before final release execution, repeat all v0.3.0 absence checks. Stop if any already exists; never overwrite it.
|
|
57
|
+
|
|
58
|
+
## Release workflow audit
|
|
59
|
+
|
|
60
|
+
The inspected `.github/workflows/release.yml` is unchanged. It triggers on tag pushes matching `v*`; the Ruby 3.3 / 3.4 / 4.0 test matrix gates `publish` through `needs: test`. Publication runs on Ruby 4.0 in environment `release`, grants GitHub OIDC `id-token: write`, checks tag/version and public metadata, configures RubyGems credentials with `rubygems/configure-rubygems-credentials`, then runs `gem push`.
|
|
61
|
+
|
|
62
|
+
- [x] Workflow trigger, test matrix, publish dependency, Ruby version, environment, OIDC permission, tag/version gate, metadata gate, and publication command audited.
|
|
63
|
+
- [ ] Confirm the GitHub release environment and RubyGems Trusted Publisher manually before the final tag action.
|
|
64
|
+
|
|
65
|
+
## Release-note examples and content
|
|
66
|
+
|
|
67
|
+
- [x] Release notes were compared with `docs/spec-v0.3.md`, `README.md`, implementation, and tests for alias resolution, Class-object normalization, exact Client Strings, JC004/JC005, JC007 reasons, schema 3 fields, and config schema 1.
|
|
68
|
+
- [x] The alias integration scenarios and full local suite passed against the unchanged candidate implementation.
|
|
69
|
+
|
|
70
|
+
## Tag gate
|
|
71
|
+
|
|
72
|
+
**Pushing `v0.3.0` triggers the Release workflow and may publish `jobcompat 0.3.0` automatically. Treat tag push as a publication action.** Before any separately approved final release execution:
|
|
73
|
+
|
|
74
|
+
- [ ] Release-preparation commit is fixed and its exact SHA identified.
|
|
75
|
+
- [ ] Exact release-preparation SHA has green official CI, including Ruby 3.3, Ruby 3.4, and Ruby 4.0.
|
|
76
|
+
- [ ] GitHub `release` environment gate has been manually verified.
|
|
77
|
+
- [ ] RubyGems Trusted Publisher settings have been manually verified.
|
|
78
|
+
- [ ] RubyGems `0.3.0`, local and remote `v0.3.0` tags, and GitHub Release `v0.3.0` are still absent.
|
|
79
|
+
- [ ] Separate final release execution approval has been obtained.
|
|
80
|
+
|
|
81
|
+
## Future tag requirements
|
|
82
|
+
|
|
83
|
+
Documented for the separately approved release-execution phase; do not perform during preparation:
|
|
84
|
+
|
|
85
|
+
- Create an annotated `v0.3.0` tag pointing exactly to the verified release-preparation commit, not `642e2c4c838db78d4e7ae6dd9ff3b958b6336483`.
|
|
86
|
+
- Never create a lightweight tag, force a tag, or move an existing tag.
|
|
87
|
+
|
|
88
|
+
## Release workflow verification
|
|
89
|
+
|
|
90
|
+
- [ ] Release workflow triggered by `v0.3.0`.
|
|
91
|
+
- [ ] Workflow tag resolves to the approved release-candidate SHA.
|
|
92
|
+
- [ ] Ruby 3.3, Ruby 3.4, and Ruby 4.0 test jobs succeed.
|
|
93
|
+
- [ ] Publish job starts only after tests; environment approval succeeds.
|
|
94
|
+
- [ ] Tag/version and metadata gates succeed.
|
|
95
|
+
- [ ] Publish-job tests and gem build succeed.
|
|
96
|
+
- [ ] OIDC credentials setup and gem push succeed.
|
|
97
|
+
- [ ] RubyGems publicly lists version `0.3.0`; do not infer publication solely from workflow status.
|
|
98
|
+
|
|
99
|
+
## Post-publication
|
|
100
|
+
|
|
101
|
+
- [ ] Verify RubyGems version `0.3.0`, Ruby `>= 3.3`, Prism `>= 1.9, < 2`, metadata URLs, and package contents.
|
|
102
|
+
- [ ] Install from public RubyGems in a fresh environment and verify `jobcompat --version`.
|
|
103
|
+
- [ ] Run representative CLI checks against the installed public gem.
|
|
104
|
+
- [ ] Create or verify GitHub Release `v0.3.0` using the reviewed release notes and confirm it points to the correct tag.
|
|
105
|
+
- [ ] Record the release workflow URL.
|
|
106
|
+
|
|
107
|
+
## Rollback
|
|
108
|
+
|
|
109
|
+
Do not overwrite or reuse `0.3.0`, move `v0.3.0`, or rewrite existing release history after publication. Fix forward with a later version. If a published gem must be removed, follow the official [RubyGems yank procedure](https://guides.rubygems.org/removing-a-published-gem/); yanking does not remove copies already installed.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# jobcompat v0.3.0 release notes
|
|
2
|
+
|
|
3
|
+
jobcompat v0.3.0 adds static worker-rename compatibility analysis for native Sidekiq. Supported aliases can keep old serialized worker names resolvable during rolling deployments. The release also models the difference between Sidekiq producers that pass a Class object and Client producers that pass an exact String identity.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- Recognize supported static compatibility aliases such as `OldJob = NewJob`.
|
|
8
|
+
- Resolve finite alias chains and supported namespaces conservatively.
|
|
9
|
+
- Avoid a false JC004 when a retained old serialized identity still resolves through a safe alias.
|
|
10
|
+
- Normalize Class-object producer identities through aliases to their terminal Class names.
|
|
11
|
+
- Preserve exact `Sidekiq::Client` String identities.
|
|
12
|
+
- Report relevant alias uncertainty as JC007 instead of assuming compatibility.
|
|
13
|
+
- Upgrade JSON output to schema version 3 and include alias provenance.
|
|
14
|
+
|
|
15
|
+
## Safe worker rename compatibility aliases
|
|
16
|
+
|
|
17
|
+
For a rename, keep a static alias while old payloads may still be retained:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
class NewExportJob
|
|
21
|
+
include Sidekiq::Job
|
|
22
|
+
|
|
23
|
+
def perform(id)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
OldExportJob = NewExportJob
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Queued, retried, or scheduled payloads already serialized as `"OldExportJob"` can resolve through this alias to the `NewExportJob` contract. A safe alias can therefore prevent JC004 for `OldExportJob` while those payloads may still run.
|
|
31
|
+
|
|
32
|
+
An alias does not hide a real argument incompatibility. If the terminal worker no longer accepts an old payload's positional arity, jobcompat can still report JC001 or JC006 under the existing rules.
|
|
33
|
+
|
|
34
|
+
Supported aliases are unconditional plain constant assignments, including supported namespace forms. Finite chains are resolved; for example, `LegacyExportJob = PreviousExportJob` and `PreviousExportJob = ExportJob` make `ExportJob` the effective consumer. This is a documented static subset, not general Ruby constant or alias resolution.
|
|
35
|
+
|
|
36
|
+
## Serialized identity: Class objects vs exact Strings
|
|
37
|
+
|
|
38
|
+
Ruby aliases refer to the same Class object. Given:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
OldExportJob = NewExportJob
|
|
42
|
+
OldExportJob.perform_async(1)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
the source token is `OldExportJob`, but the value passed to Sidekiq is the `NewExportJob` Class object. Sidekiq serializes that object's canonical name, so jobcompat models the persisted identity as `NewExportJob`. During a rolling deployment, activating this producer can still produce JC005 for `NewExportJob` if the old fleet does not recognize that name.
|
|
46
|
+
|
|
47
|
+
**Keeping `OldExportJob = NewExportJob` protects retained old `"OldExportJob"` payloads, but calling `OldExportJob.perform_async(...)` is not a mechanism for continuing to enqueue the old serialized class name.**
|
|
48
|
+
|
|
49
|
+
An explicit Client String has different identity behavior:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
Sidekiq::Client.push(
|
|
53
|
+
"class" => "OldExportJob",
|
|
54
|
+
"args" => [1]
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Here the serialized identity remains exactly `OldExportJob`; jobcompat models that String as-is and can resolve it through the consumer-side alias. Client String pushes are described to explain identity modeling, not as the default rename strategy; their job-option and default semantics differ.
|
|
59
|
+
|
|
60
|
+
## Conservative alias uncertainty
|
|
61
|
+
|
|
62
|
+
jobcompat does not assume compatibility when Ruby behavior is ambiguous. Relevant uncertainty is reported as JC007 with one of these reasons:
|
|
63
|
+
|
|
64
|
+
- `alias_target_unresolved`: the target cannot be proven to end at a recognized worker.
|
|
65
|
+
- `alias_cycle`: aliases refer back to each other.
|
|
66
|
+
- `alias_binding_conflict`: the same constant has conflicting definitions or writes.
|
|
67
|
+
- `unsupported_alias_assignment`: the assignment is conditional, dynamic, operator-based, or otherwise outside the supported syntax.
|
|
68
|
+
|
|
69
|
+
Unknown does not mean compatible. jobcompat does not execute application source to resolve these cases. It supports plain static assignments such as `OldJob = NewJob`, supported qualified paths, and supported namespace bodies. It does not resolve `const_set`, `autoload`, `const_missing`, conditional aliases, dynamic right-hand-side evaluation, runtime load order, general Ruby constant evaluation, or heuristic renames.
|
|
70
|
+
|
|
71
|
+
## JSON schema 3
|
|
72
|
+
|
|
73
|
+
**v0.3.0 changes JSON `schema_version` from 2 to 3.** `workers[]` now represents compared serialized worker identities and exposes alias provenance. Each worker result includes `base_alias` and `head_alias`; the presence value `resolved_alias` identifies a successfully resolved alias. `summary.workers.base` and `.head` continue to count directly recognized Sidekiq workers, not aliases.
|
|
74
|
+
|
|
75
|
+
The `.jobcompat.yml` configuration schema remains `version: 1`. Do not change the configuration version to 3.
|
|
76
|
+
|
|
77
|
+
## Compatibility contracts
|
|
78
|
+
|
|
79
|
+
No new JC rule was added. JC001–JC007 IDs, severities, JC001/JC003 precedence, the three rolling-deployment directions, exit codes, and suppression identity remain unchanged. The configuration schema remains version 1, Ruby `>= 3.3` remains required, and Prism `>= 1.9, < 2` remains the supported range.
|
|
80
|
+
|
|
81
|
+
## Upgrade notes
|
|
82
|
+
|
|
83
|
+
**Consumers that parse jobcompat JSON must update to accept `schema_version: 3` before upgrading to v0.3.0.** Do not assume that a schema 2 consumer can read schema 3 unless that consumer explicitly supports new schema versions and fields.
|
|
84
|
+
|
|
85
|
+
No `.jobcompat.yml` version change is needed. Keep a compatibility alias for as long as old serialized identities can remain in queues, retries, schedules, or other retained payloads. Plan producer activation separately: calling an aliased Class object's Sidekiq API serializes the terminal Class name.
|
|
86
|
+
|
|
87
|
+
## Known limitations
|
|
88
|
+
|
|
89
|
+
Analysis covers positional arity for native Sidekiq workers only. ActiveJob and keyword compatibility are unsupported. jobcompat does not inspect Hash internal schemas, payload value types, live Redis contents, general dataflow, arbitrary Client instances, wrapper inference, runtime load order, or general aliasing and metaprogramming. It does not execute application code.
|
|
90
|
+
|
|
91
|
+
No repository producer callsite does not prove an empty queue. Queued, scheduled, retried, historical, and external jobs may still exist; static analysis does not establish runtime queue contents.
|
|
92
|
+
|
|
93
|
+
## Installation
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
gem install jobcompat --version 0.3.0
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Bundler users can add:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
gem "jobcompat", "~> 0.3.0", require: false
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Then fetch the comparison ref and run:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
jobcompat check --base origin/main
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
or:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
bundle exec jobcompat check --base origin/main
|
|
115
|
+
```
|