paper_trail_diff 0.8.0 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41f2b97428f5dc66c22e29bb1a01cdda0beb1ad29eafe5f86ce8809fb1c61bc0
4
- data.tar.gz: 9f6ef5ebef159decfaf875b6df4e6341f6243c9431085b65a67987baa674191f
3
+ metadata.gz: 00ac8585544b9421fab5fa7cf677a5eddaae2867058beb2c7958ff0e8f917da5
4
+ data.tar.gz: 52e079e4f84b77ffd804fd452eaf4acc03618d2e7e685969faddcfe2f8c21eba
5
5
  SHA512:
6
- metadata.gz: 28d69434ddf8c9886f1785dc665a54040c38721e1aae84738f50668f2cf677cd39f8a3f9870cc06f3627a0c9f134b935a26dceb2f50c2eff8634bd2dc52bbc8f
7
- data.tar.gz: 704c54404a71a76a9deaa6f3e7d03b425763fdce010b240f8e24e4a293d8656c448a11bcf0e5eabcf6e3d0c2c7bb83b6d277acad83e27a7a749bdb1f0116edf5
6
+ metadata.gz: 400361a0bd6d3d55b3637a5fdcb42febf04ff62a81b75aba3848f70c62d1865f7983016c31bb34347dc50df5b2f66ccb0e44306e2a31990c3db5c04f3806813e
7
+ data.tar.gz: 0b8497f1dcb9ecd6e12b09e28b7a69fe17363d251308abffb824e8d1d0033572c4bee71351785ed425dc5e46c656d092612c7cb4c1ff33e69ad270d8e2e7260b
data/CHANGELOG.md CHANGED
@@ -3,6 +3,46 @@
3
3
  All notable changes to this project will be documented in this file. The
4
4
  project follows [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.10.0] - 2026-08-15
7
+
8
+ ### Added
9
+
10
+ - Add `rake release:preflight`, which runs the full gate and then checks
11
+ everything that must hold before a tag: a clean working tree, a HEAD that
12
+ matches its upstream, an unused tag, a dated changelog section for the
13
+ version, and that the version is not already on RubyGems. It reports and
14
+ never tags, pushes, or publishes. An unreachable RubyGems is reported as
15
+ unknown rather than treated as "not published".
16
+ - Raise `PaperTrailDiff::UnversionedAssociationError` when a selected
17
+ association's target is not versioned and history is being reconstructed.
18
+ Such a comparison could only ever answer "nothing changed", which is a wrong
19
+ answer rather than an empty one. Live-to-live comparison reads current state
20
+ and is unaffected. `diagnose` now reports the same condition as an error
21
+ rather than a warning, so `ok?` no longer stays true for a comparison that
22
+ will raise.
23
+ - Document ActiveStorage attachments, which this reaches through:
24
+ `has_one_attached` points at models Rails owns and PaperTrail never versions,
25
+ so attachments are audited through a versioned model of your own that mirrors
26
+ the metadata onto ordinary columns.
27
+
28
+ ## [0.9.0] - 2026-08-14
29
+
30
+ ### Added
31
+
32
+ - Report versions that share a timestamp, which hides any association change
33
+ between them: PT-AT records membership per version but resolves it by
34
+ timestamp, so such a pair is indistinguishable even when sequential ids order
35
+ it correctly. `diagnose` reports `:tied_version_timestamps` as a warning when
36
+ `associations:` are selected, and a comparison in that state emits
37
+ `ambiguous_association_boundary.paper_trail_diff`. It warns rather than
38
+ raising because the result may be perfectly correct — if nothing associated
39
+ changed, nothing was lost — and the gem cannot distinguish the cases, since
40
+ not seeing the change is the symptom.
41
+
42
+ ### Changed
43
+
44
+ - Link the demo application from the README now that it is public.
45
+
6
46
  ## [0.8.0] - 2026-08-12
7
47
 
8
48
  ### Added
data/README.md CHANGED
@@ -29,7 +29,10 @@ which fetches what it needs on first run:
29
29
  ruby demo.rb
30
30
  ```
31
31
 
32
- Then start with the copyable [Quickstart](QUICKSTART.md).
32
+ Then start with the copyable [Quickstart](QUICKSTART.md), or browse
33
+ [the demo application](https://github.com/aheathwilliams/paper_trail_summary_test_app)
34
+ — a small Rails app that installs a published release and exercises scalar,
35
+ nested, through-association, and HABTM history against it.
33
36
 
34
37
  ## Installation
35
38
 
@@ -124,6 +127,80 @@ running rather than after a surprise.
124
127
  Either half alone is fine. Sequential ids order tied timestamps correctly, and
125
128
  distinct timestamps never reach the fallback.
126
129
 
130
+ ### Association changes across a shared timestamp
131
+
132
+ Sequential ids recover the *order* of tied versions, but not their association
133
+ state. PT-AT records association membership per version and resolves it by
134
+ timestamp, so two versions sharing one are indistinguishable for that purpose:
135
+ **an association change between them is invisible.** It is not reported as
136
+ absent — the association simply does not appear in the diff.
137
+
138
+ Unlike an unorderable sequence this is not always wrong. If nothing associated
139
+ changed between the two versions, the result is correct, and the gem cannot
140
+ tell the two cases apart, because not seeing the change is the symptom. So it
141
+ does not raise. Instead:
142
+
143
+ - `diagnose` reports `:tied_version_timestamps` as a **warning** whenever
144
+ `associations:` are selected and versions in range share a timestamp.
145
+ - a comparison in that state emits
146
+ `ambiguous_association_boundary.paper_trail_diff` through ActiveSupport
147
+ notifications, carrying the two version ids and the shared timestamp. Nothing
148
+ is logged; subscribe if you want it surfaced.
149
+
150
+ Writing several versions inside one clock tick is easy to do — a service object
151
+ that touches a record and its children in quick succession will manage it — so
152
+ this is worth checking before trusting an association history. Recording
153
+ versions at sub-second precision separates them.
154
+
155
+ ### ActiveStorage attachments
156
+
157
+ `has_one_attached` and `has_many_attached` point at `ActiveStorage::Attachment`
158
+ and `ActiveStorage::Blob`, which Rails owns and PaperTrail does not version.
159
+ There is no history behind them, so a historical comparison over those paths
160
+ raises `PaperTrailDiff::UnversionedAssociationError` rather than reporting that
161
+ nothing changed. Adding `has_paper_trail` to Rails' own models does not rescue
162
+ it either: `has_one_attached` is a scoped `has_one`, and reifying it from
163
+ history is ambiguous.
164
+
165
+ Audit the attachment through a model you own instead. Give it the facts worth
166
+ auditing as ordinary columns, and version it:
167
+
168
+ ```ruby
169
+ class DocumentRevision < ApplicationRecord
170
+ belongs_to :attachable, polymorphic: true
171
+ has_one_attached :file
172
+ has_paper_trail
173
+
174
+ # Attaching a file writes to ActiveStorage's tables, not to this record, so
175
+ # nothing would be versioned without copying the facts across.
176
+ after_save :record_file_metadata, if: -> { file.attached? }
177
+
178
+ def record_file_metadata
179
+ blob = file.blob
180
+ return if filename == blob.filename.to_s && checksum == blob.checksum
181
+
182
+ update_columns(
183
+ filename: blob.filename.to_s, content_type: blob.content_type,
184
+ byte_size: blob.byte_size, checksum: blob.checksum
185
+ )
186
+ end
187
+ end
188
+ ```
189
+
190
+ ```ruby
191
+ class Article < ApplicationRecord
192
+ has_many :document_revisions, as: :attachable
193
+ has_paper_trail
194
+ end
195
+
196
+ PaperTrailDiff.compare(before, after, associations: [:document_revisions])
197
+ ```
198
+
199
+ The bytes stay in ActiveStorage; the *auditable facts* live where PaperTrail
200
+ can see them. Nothing about the column names matters to this gem — it reports
201
+ whichever columns changed. What matters is that replacing a file writes to a
202
+ versioned record, because attaching one on its own does not.
203
+
127
204
  ## Choosing an entry point
128
205
 
129
206
  | You need | Call |
@@ -79,7 +79,7 @@ module PaperTrailDiff
79
79
  # Runs whether or not associations are selected: unorderable versions
80
80
  # corrupt a scalar timeline just as surely, and a report that inspected
81
81
  # nothing has no business answering `ok?`.
82
- inspect_version_order
82
+ inspect_version_sequence
83
83
  return DiagnosticReport.new(issues: @issues) if @tree.empty?
84
84
 
85
85
  unless association_tracking_available?
@@ -136,29 +136,13 @@ module PaperTrailDiff
136
136
  inspect_transaction_metadata(paths)
137
137
  end
138
138
 
139
- # Ordering falls back to the id when timestamps tie, which only recovers the
140
- # real sequence for ids that increase with insertion. Reported before a run
141
- # rather than after a wrong answer.
142
139
  #: () -> void
143
- def inspect_version_order
144
- pair = Support.ambiguous_pair(ordered_range_versions)
145
- return unless pair
146
-
147
- add_error(:ambiguous_version_order, Support.ambiguous_message(pair), nil, pair.first.id)
148
- rescue StandardError
149
- nil
150
- end
151
-
152
- #: () -> Array[untyped]
153
- def ordered_range_versions
154
- bounds = [@from_version.created_at, @to_version.created_at].compact.sort
155
- return [] unless bounds.length == 2
156
-
157
- @from_version.class
158
- .where(item_type: @from_version.item_type, item_id: @from_version.item_id)
159
- .where(created_at: bounds.first..bounds.last)
160
- .to_a
161
- .sort_by { |version| Support.chronological_version_key(version) }
140
+ def inspect_version_sequence
141
+ @issues.concat(
142
+ VersionSequenceDiagnostics.new(
143
+ @from_version, @to_version, associations_selected: !@tree.empty?
144
+ ).call
145
+ )
162
146
  end
163
147
 
164
148
  #: (untyped) -> void
@@ -191,7 +175,9 @@ module PaperTrailDiff
191
175
  def inspect_versioned_model(model_class, path)
192
176
  return if model_versioned?(model_class)
193
177
 
194
- add_warning(
178
+ # An error rather than a warning: `ok?` must not stay true for a
179
+ # comparison that cannot work, and this one raises at runtime.
180
+ add_error(
195
181
  :unversioned_association_target,
196
182
  "#{model_class.name} does not appear to have PaperTrail enabled",
197
183
  path
@@ -35,6 +35,10 @@ module PaperTrailDiff
35
35
  # Raised when a requested ActiveRecord association does not exist.
36
36
  class UnknownAssociationError < Error; end
37
37
 
38
+ # Raised when a selected association's target is not versioned, so its history
39
+ # cannot be reconstructed at all.
40
+ class UnversionedAssociationError < Error; end
41
+
38
42
  # Raised when a requested association macro is not supported.
39
43
  class UnsupportedAssociationError < Error; end
40
44
 
@@ -19,6 +19,14 @@ module PaperTrailDiff
19
19
  )
20
20
  end
21
21
 
22
+ # Reports a condition rather than timing work: nothing failed, but the
23
+ # result may be incomplete and only the application can judge that.
24
+ #: (String | Symbol, Hash[Symbol, untyped]) -> void
25
+ def notify(event, payload)
26
+ instrument(event, payload) { nil }
27
+ nil
28
+ end
29
+
22
30
  #: (association_paths: Array[String], reload_live_endpoints: bool) -> Hash[Symbol, untyped]
23
31
  def comparison_payload(association_paths:, reload_live_endpoints:)
24
32
  {
@@ -33,6 +33,7 @@ module PaperTrailDiff
33
33
  payload = @instrumentation_payload.merge(comparison_count: 1)
34
34
  Instrumentation.instrument('compare', payload) do
35
35
  Endpoint.validate_pair!(from_endpoint, to_endpoint)
36
+ notify_ambiguous_association_boundary(from_endpoint, to_endpoint)
36
37
  Engine.compare(snapshot_for_endpoint(from_endpoint), snapshot_for_endpoint(to_endpoint))
37
38
  end
38
39
  end
@@ -141,6 +142,27 @@ module PaperTrailDiff
141
142
  ).analyze
142
143
  end
143
144
 
145
+ # Association membership is resolved by timestamp, so endpoints sharing one
146
+ # cannot be told apart and any association change between them is invisible.
147
+ # The result may still be correct -- nothing associated may have changed --
148
+ # and the gem cannot tell which, since not seeing the change is the symptom.
149
+ # So it reports the condition and leaves the judgement to the application.
150
+ #: (untyped, untyped) -> void
151
+ def notify_ambiguous_association_boundary(from_endpoint, to_endpoint)
152
+ return if @association_tree.empty?
153
+ return unless Endpoint.version?(from_endpoint) && Endpoint.version?(to_endpoint)
154
+ return unless from_endpoint.created_at == to_endpoint.created_at
155
+
156
+ Instrumentation.notify(
157
+ 'ambiguous_association_boundary',
158
+ @instrumentation_payload.merge(
159
+ item_type: from_endpoint.item_type.to_s,
160
+ version_ids: [from_endpoint.id, to_endpoint.id].freeze,
161
+ recorded_at: from_endpoint.created_at
162
+ )
163
+ )
164
+ end
165
+
144
166
  # `close_on:` names what ends a wall-clock window, so it is meaningless for a
145
167
  # range whose endpoints the caller already gave explicitly.
146
168
  #: (Symbol?, untyped) -> bool
@@ -72,6 +72,15 @@ module PaperTrailDiff
72
72
  end
73
73
  end
74
74
 
75
+ # Versions sharing a timestamp, whether or not their ids order them. PT-AT
76
+ # indexes association membership per version but resolves it by timestamp,
77
+ # so association state cannot be told apart across such a pair even when the
78
+ # scalar sequence is perfectly recoverable.
79
+ #: (Array[untyped]) -> Array[untyped]?
80
+ def tied_timestamp_pair(versions)
81
+ versions.each_cons(2).find { |left, right| left.created_at == right.created_at }
82
+ end
83
+
75
84
  #: (untyped) -> bool
76
85
  def sequential_id?(id)
77
86
  id.is_a?(Integer) || id.to_s.match?(/\A\d+\z/)
@@ -17,6 +17,7 @@ module PaperTrailDiff
17
17
 
18
18
  ensure_association_tracking! if historical
19
19
  @traversal.validate!(model_class)
20
+ ensure_versioned_targets!(model_class) if historical
20
21
  end
21
22
 
22
23
  private
@@ -24,6 +25,30 @@ module PaperTrailDiff
24
25
  # @rbs @tree: AssociationTree
25
26
  # @rbs @traversal: AssociationTraversal
26
27
 
28
+ # A model PaperTrail never versioned has no history to reconstruct, so a
29
+ # comparison over it can only ever answer "nothing changed" -- which is a
30
+ # wrong answer rather than an empty one. Live-to-live comparison reads
31
+ # current state and is unaffected, so this applies to historical work only.
32
+ #: (untyped) -> void
33
+ def ensure_versioned_targets!(model_class)
34
+ @traversal.selected_reflections(model_class).each do |path, reflection|
35
+ # A polymorphic target is not known until a row names it; `diagnose`
36
+ # reports that separately rather than guessing here.
37
+ next if reflection.polymorphic?
38
+ next if versioned?(reflection.klass)
39
+
40
+ raise UnversionedAssociationError,
41
+ "association #{path} cannot be compared historically: " \
42
+ "#{reflection.klass.name} is not versioned. Add `has_paper_trail` to it, " \
43
+ 'or mirror the fields you need onto a model that has it.'
44
+ end
45
+ end
46
+
47
+ #: (untyped) -> bool
48
+ def versioned?(model_class)
49
+ model_class.respond_to?(:paper_trail_options) && !model_class.paper_trail_options.nil?
50
+ end
51
+
27
52
  #: () -> void
28
53
  def ensure_association_tracking!
29
54
  paper_trail = Object.const_get(:PaperTrail) #: untyped
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module PaperTrailDiff
5
- VERSION = '0.8.0'
5
+ VERSION = '0.10.0'
6
6
  end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module PaperTrailDiff
5
+ # Checks the recorded sequence itself, before any question of association
6
+ # setup: whether the versions can be ordered at all, and whether timestamps
7
+ # they share hide changes that ordering alone cannot recover.
8
+ class VersionSequenceDiagnostics
9
+ #: (untyped, untyped, ?associations_selected: bool) -> void
10
+ def initialize(from_version, to_version, associations_selected: false)
11
+ @from_version = from_version
12
+ @to_version = to_version
13
+ @associations_selected = associations_selected
14
+ end
15
+
16
+ #: () -> Array[DiagnosticIssue]
17
+ def call
18
+ versions = ordered_range_versions
19
+ issues = [] #: Array[DiagnosticIssue?]
20
+ issues << unorderable(versions)
21
+ issues << tied_timestamps(versions) if @associations_selected
22
+ issues.compact
23
+ rescue StandardError
24
+ []
25
+ end
26
+
27
+ private
28
+
29
+ # @rbs @from_version: untyped
30
+ # @rbs @to_version: untyped
31
+ # @rbs @associations_selected: bool
32
+
33
+ # Ordering falls back to the id when timestamps tie, which only recovers the
34
+ # real sequence for ids that increase with insertion. Reported before a run
35
+ # rather than after a wrong answer.
36
+ #: (Array[untyped]) -> DiagnosticIssue?
37
+ def unorderable(versions)
38
+ pair = Support.ambiguous_pair(versions)
39
+ return unless pair
40
+
41
+ DiagnosticIssue.new(
42
+ severity: :error,
43
+ code: :ambiguous_version_order,
44
+ message: Support.ambiguous_message(pair),
45
+ version_id: pair.first.id
46
+ )
47
+ end
48
+
49
+ # Association membership is recorded per version but resolved by timestamp,
50
+ # so a tie hides any association change across that pair. Unlike an
51
+ # unorderable sequence this is not always wrong: if nothing associated
52
+ # changed between them the result is correct, and the gem cannot tell which
53
+ # it is, because not seeing the change is the symptom. So it warns.
54
+ #: (Array[untyped]) -> DiagnosticIssue?
55
+ def tied_timestamps(versions)
56
+ pair = Support.tied_timestamp_pair(versions)
57
+ return unless pair
58
+
59
+ left, right = pair
60
+ DiagnosticIssue.new(
61
+ severity: :warning,
62
+ code: :tied_version_timestamps,
63
+ message: "versions #{left.id.inspect} and #{right.id.inspect} share the timestamp " \
64
+ "#{left.created_at.inspect}, so association changes between them cannot " \
65
+ 'be detected; record versions at sub-second precision to separate them',
66
+ version_id: left.id
67
+ )
68
+ end
69
+
70
+ #: () -> Array[untyped]
71
+ def ordered_range_versions
72
+ bounds = [@from_version.created_at, @to_version.created_at].compact.sort
73
+ return [] unless bounds.length == 2
74
+
75
+ @from_version.class
76
+ .where(item_type: @from_version.item_type, item_id: @from_version.item_id)
77
+ .where(created_at: bounds.first..bounds.last)
78
+ .to_a
79
+ .sort_by { |version| Support.chronological_version_key(version) }
80
+ end
81
+ end
82
+ end
@@ -65,6 +65,7 @@ require_relative 'paper_trail_diff/activity_root_steps'
65
65
  require_relative 'paper_trail_diff/analysis_batch'
66
66
  require_relative 'paper_trail_diff/batched_root_analyzer'
67
67
  require_relative 'paper_trail_diff/version_range'
68
+ require_relative 'paper_trail_diff/version_sequence_diagnostics'
68
69
  require_relative 'paper_trail_diff/time_range'
69
70
  require_relative 'paper_trail_diff/time_version_range'
70
71
  require_relative 'paper_trail_diff/timeline_range'
@@ -67,14 +67,8 @@ module PaperTrailDiff
67
67
  # : (Array[Array[untyped]]) -> void
68
68
  def inspect_habtm: (Array[Array[untyped]]) -> void
69
69
 
70
- # Ordering falls back to the id when timestamps tie, which only recovers the
71
- # real sequence for ids that increase with insertion. Reported before a run
72
- # rather than after a wrong answer.
73
70
  # : () -> void
74
- def inspect_version_order: () -> void
75
-
76
- # : () -> Array[untyped]
77
- def ordered_range_versions: () -> Array[untyped]
71
+ def inspect_version_sequence: () -> void
78
72
 
79
73
  # : (untyped) -> void
80
74
  def inspect_checkpoint_timestamp: (untyped) -> void
@@ -45,6 +45,11 @@ module PaperTrailDiff
45
45
  class UnknownAssociationError < Error
46
46
  end
47
47
 
48
+ # Raised when a selected association's target is not versioned, so its history
49
+ # cannot be reconstructed at all.
50
+ class UnversionedAssociationError < Error
51
+ end
52
+
48
53
  # Raised when a requested association macro is not supported.
49
54
  class UnsupportedAssociationError < Error
50
55
  end
@@ -6,6 +6,11 @@ module PaperTrailDiff
6
6
  # : (String | Symbol, Hash[Symbol, untyped]) { () -> untyped } -> untyped
7
7
  def self?.instrument: (String | Symbol, Hash[Symbol, untyped]) { () -> untyped } -> untyped
8
8
 
9
+ # Reports a condition rather than timing work: nothing failed, but the
10
+ # result may be incomplete and only the application can judge that.
11
+ # : (String | Symbol, Hash[Symbol, untyped]) -> void
12
+ def self?.notify: (String | Symbol, Hash[Symbol, untyped]) -> void
13
+
9
14
  # : (association_paths: Array[String], reload_live_endpoints: bool) -> Hash[Symbol, untyped]
10
15
  def self?.comparison_payload: (association_paths: Array[String], reload_live_endpoints: bool) -> Hash[Symbol, untyped]
11
16
  end
@@ -59,6 +59,14 @@ module PaperTrailDiff
59
59
  # : (untyped, from: untyped, to: untyped, within: untyped, version_scope: untyped, live_endpoint: untyped, ?snapshots: bool) -> Analysis
60
60
  def analyze_activity: (untyped, from: untyped, to: untyped, within: untyped, version_scope: untyped, live_endpoint: untyped, ?snapshots: bool) -> Analysis
61
61
 
62
+ # Association membership is resolved by timestamp, so endpoints sharing one
63
+ # cannot be told apart and any association change between them is invisible.
64
+ # The result may still be correct -- nothing associated may have changed --
65
+ # and the gem cannot tell which, since not seeing the change is the symptom.
66
+ # So it reports the condition and leaves the judgement to the application.
67
+ # : (untyped, untyped) -> void
68
+ def notify_ambiguous_association_boundary: (untyped, untyped) -> void
69
+
62
70
  # `close_on:` names what ends a wall-clock window, so it is meaningless for a
63
71
  # range whose endpoints the caller already gave explicitly.
64
72
  # : (Symbol?, untyped) -> bool
@@ -25,6 +25,13 @@ module PaperTrailDiff
25
25
  # : (Array[untyped]) -> Array[untyped]?
26
26
  def self?.ambiguous_pair: (Array[untyped]) -> Array[untyped]?
27
27
 
28
+ # Versions sharing a timestamp, whether or not their ids order them. PT-AT
29
+ # indexes association membership per version but resolves it by timestamp,
30
+ # so association state cannot be told apart across such a pair even when the
31
+ # scalar sequence is perfectly recoverable.
32
+ # : (Array[untyped]) -> Array[untyped]?
33
+ def self?.tied_timestamp_pair: (Array[untyped]) -> Array[untyped]?
34
+
28
35
  # : (untyped) -> bool
29
36
  def self?.sequential_id?: (untyped) -> bool
30
37
 
@@ -16,6 +16,16 @@ module PaperTrailDiff
16
16
 
17
17
  @tree: AssociationTree
18
18
 
19
+ # A model PaperTrail never versioned has no history to reconstruct, so a
20
+ # comparison over it can only ever answer "nothing changed" -- which is a
21
+ # wrong answer rather than an empty one. Live-to-live comparison reads
22
+ # current state and is unaffected, so this applies to historical work only.
23
+ # : (untyped) -> void
24
+ def ensure_versioned_targets!: (untyped) -> void
25
+
26
+ # : (untyped) -> bool
27
+ def versioned?: (untyped) -> bool
28
+
19
29
  # : () -> void
20
30
  def ensure_association_tracking!: () -> void
21
31
  end
@@ -0,0 +1,39 @@
1
+ # Generated from lib/paper_trail_diff/version_sequence_diagnostics.rb with RBS::Inline
2
+
3
+ module PaperTrailDiff
4
+ # Checks the recorded sequence itself, before any question of association
5
+ # setup: whether the versions can be ordered at all, and whether timestamps
6
+ # they share hide changes that ordering alone cannot recover.
7
+ class VersionSequenceDiagnostics
8
+ # : (untyped, untyped, ?associations_selected: bool) -> void
9
+ def initialize: (untyped, untyped, ?associations_selected: bool) -> void
10
+
11
+ # : () -> Array[DiagnosticIssue]
12
+ def call: () -> Array[DiagnosticIssue]
13
+
14
+ private
15
+
16
+ @associations_selected: bool
17
+
18
+ @from_version: untyped
19
+
20
+ @to_version: untyped
21
+
22
+ # Ordering falls back to the id when timestamps tie, which only recovers the
23
+ # real sequence for ids that increase with insertion. Reported before a run
24
+ # rather than after a wrong answer.
25
+ # : (Array[untyped]) -> DiagnosticIssue?
26
+ def unorderable: (Array[untyped]) -> DiagnosticIssue?
27
+
28
+ # Association membership is recorded per version but resolved by timestamp,
29
+ # so a tie hides any association change across that pair. Unlike an
30
+ # unorderable sequence this is not always wrong: if nothing associated
31
+ # changed between them the result is correct, and the gem cannot tell which
32
+ # it is, because not seeing the change is the symptom. So it warns.
33
+ # : (Array[untyped]) -> DiagnosticIssue?
34
+ def tied_timestamps: (Array[untyped]) -> DiagnosticIssue?
35
+
36
+ # : () -> Array[untyped]
37
+ def ordered_range_versions: () -> Array[untyped]
38
+ end
39
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paper_trail_diff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Williams
@@ -118,6 +118,7 @@ files:
118
118
  - lib/paper_trail_diff/version_association_candidate_scope.rb
119
119
  - lib/paper_trail_diff/version_range.rb
120
120
  - lib/paper_trail_diff/version_scope_filter.rb
121
+ - lib/paper_trail_diff/version_sequence_diagnostics.rb
121
122
  - sig/generated/paper_trail_diff.rbs
122
123
  - sig/generated/paper_trail_diff/activity_belongs_to_event_applier.rbs
123
124
  - sig/generated/paper_trail_diff/activity_boundary.rbs
@@ -194,17 +195,18 @@ files:
194
195
  - sig/generated/paper_trail_diff/version_association_candidate_scope.rbs
195
196
  - sig/generated/paper_trail_diff/version_range.rbs
196
197
  - sig/generated/paper_trail_diff/version_scope_filter.rbs
198
+ - sig/generated/paper_trail_diff/version_sequence_diagnostics.rbs
197
199
  homepage: https://github.com/aheathwilliams/paper_trail_diff
198
200
  licenses:
199
201
  - MIT
200
202
  metadata:
201
203
  allowed_push_host: https://rubygems.org
202
204
  bug_tracker_uri: https://github.com/aheathwilliams/paper_trail_diff/issues
203
- changelog_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.8.0/CHANGELOG.md
204
- documentation_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.8.0/README.md
205
+ changelog_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.10.0/CHANGELOG.md
206
+ documentation_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.10.0/README.md
205
207
  homepage_uri: https://github.com/aheathwilliams/paper_trail_diff
206
208
  rubygems_mfa_required: 'true'
207
- source_code_uri: https://github.com/aheathwilliams/paper_trail_diff/tree/v0.8.0
209
+ source_code_uri: https://github.com/aheathwilliams/paper_trail_diff/tree/v0.10.0
208
210
  rdoc_options: []
209
211
  require_paths:
210
212
  - lib