rigortype 0.4.0 → 0.4.2
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/README.md +4 -3
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/08-understanding-errors.md +3 -2
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +2 -2
- data/docs/manual/03-configuration.md +6 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/18-removing-dead-code.md +32 -2
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +184 -61
- data/lib/rigor/analysis/check_rules.rb +407 -144
- data/lib/rigor/analysis/dependency_recorder.rb +215 -18
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +291 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
- data/lib/rigor/analysis/incremental_session.rb +287 -50
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +43 -9
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +371 -49
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_paths.rb +6 -4
- data/lib/rigor/analysis/template_units.rb +15 -5
- data/lib/rigor/analysis/worker_session.rb +11 -7
- data/lib/rigor/cache/descriptor.rb +40 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +92 -8
- data/lib/rigor/cache/store.rb +63 -3
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +85 -14
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +16 -10
- data/lib/rigor/cli.rb +11 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +11 -0
- data/lib/rigor/configuration.rb +52 -7
- data/lib/rigor/effects/plugin_facts.rb +6 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/lockfile_resolver.rb +10 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +284 -15
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +34 -73
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/block_parameter_binder.rb +29 -13
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +7 -1
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +26 -3
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/expression_typer.rb +351 -61
- data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +54 -13
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +73 -14
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +305 -54
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- data/lib/rigor/inference/module_function_state.rb +214 -0
- data/lib/rigor/inference/multi_target_binder.rb +21 -16
- data/lib/rigor/inference/narrowing.rb +166 -22
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
- data/lib/rigor/inference/project_patched_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +2129 -354
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +149 -32
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/plugin/base.rb +28 -8
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +124 -7
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/discovery_seed.rb +18 -9
- data/lib/rigor/protection/mutation_cache.rb +4 -4
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +211 -46
- data/lib/rigor/reflection/constant_path.rb +33 -7
- data/lib/rigor/reflection.rb +41 -10
- data/lib/rigor/scope/discovery_index.rb +258 -3
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +1032 -0
- data/lib/rigor/scope.rb +480 -328
- data/lib/rigor/sig_gen/alias_index.rb +1 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
- data/lib/rigor/sig_gen/generator.rb +77 -21
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +59 -3
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +13 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/environment.rbs +2 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/scope.rbs +69 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- data/skills/rigor-project-init/SKILL.md +37 -11
- data/skills/rigor-project-init/references/02-configure.md +7 -2
- metadata +90 -1
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
|
|
6
|
+
require_relative "../incremental_run_slot"
|
|
7
|
+
require_relative "../dependency_source_inference/gem_resolver"
|
|
8
|
+
require_relative "../../cache/file_digest"
|
|
9
|
+
require_relative "../../cache/incremental_snapshot"
|
|
10
|
+
|
|
11
|
+
module Rigor
|
|
12
|
+
module Analysis
|
|
13
|
+
module IncrementalRunSlot
|
|
14
|
+
# ADR-45 WD2 (#1507) — whether a run may record its answer at all. Some of a slot's rows are built when the
|
|
15
|
+
# run ENDS rather than when it reads, and each of those must still describe the tree the run analysed: a
|
|
16
|
+
# save that lands while the run reads — an editor's, a `bundle install`'s, a `mkdir` — would otherwise leave
|
|
17
|
+
# the slot vouching for a tree the analysis never saw, and the probe would serve the pre-save answer against
|
|
18
|
+
# the post-save tree.
|
|
19
|
+
#
|
|
20
|
+
# So the session takes a mark before the run reads anything ({.start}) and asks {#admits?} about those rows
|
|
21
|
+
# before it writes. A row recorded as the run read (a plugin's read, a listing it took) needs no guard: a
|
|
22
|
+
# later save leaves it stale, and validation says so.
|
|
23
|
+
#
|
|
24
|
+
# The mark is the filesystem's own clock, read off the change time of a file written for the purpose: a
|
|
25
|
+
# timestamp compared across clocks misses an edit that lands inside a coarse filesystem's tick, and change
|
|
26
|
+
# times (unlike modification times) cannot be set back by `cp -p` or `touch -d`. One clock means one
|
|
27
|
+
# filesystem, so the mark is taken only when the store is on the project's filesystem, and a row the key
|
|
28
|
+
# does not pin refuses the write when it is on any other. Declining to write is always safe; it leaves the
|
|
29
|
+
# next null run on the full path.
|
|
30
|
+
#
|
|
31
|
+
# A filesystem's change times tick coarsely (a millisecond or more on ext4 and tmpfs), so a file saved just
|
|
32
|
+
# before the stamp can carry the stamp's own change time. {#admits?} refuses a change time at or after the
|
|
33
|
+
# mark, since a save after the stamp in the same tick reads the same; taking the first stamp as the mark
|
|
34
|
+
# would therefore refuse every save that landed shortly before the run, and no slot would be written. So the
|
|
35
|
+
# mark is taken on a tick boundary: stamps are written until one carries a later change time than the first,
|
|
36
|
+
# and that later change time is the mark. Everything saved before the run started carries a change time no
|
|
37
|
+
# later than the first stamp, hence before the mark; everything saved after the mark carries at least the
|
|
38
|
+
# mark. The wait is bounded ({TICK_WAIT_LIMIT}); a filesystem that does not tick within it takes no mark,
|
|
39
|
+
# which is the safe answer. A filesystem whose first stamp lands on a whole second (HFS+, FAT, ext3) ticks too
|
|
40
|
+
# coarsely to wait out and takes no mark at once.
|
|
41
|
+
#
|
|
42
|
+
# Where the change time is not the time of the write, the mark says nothing, and the guard takes none or
|
|
43
|
+
# cannot tell: native Windows, where a change time is the creation time, takes no mark; a network or FUSE
|
|
44
|
+
# filesystem (NFS with its attribute cache, virtiofs, sshfs) may report a change time from before a save,
|
|
45
|
+
# which nothing here detects. A clock that steps backwards (an NTP correction) inside the run would date a
|
|
46
|
+
# save before the mark, so {#admits?} takes one more stamp and refuses when it reads earlier than the mark.
|
|
47
|
+
# A step undone before the run ends is not seen.
|
|
48
|
+
class WriteGuard
|
|
49
|
+
STAMP_DIR = "incremental"
|
|
50
|
+
private_constant :STAMP_DIR
|
|
51
|
+
|
|
52
|
+
# How long to wait, in seconds, for the filesystem's change time to tick past the first stamp.
|
|
53
|
+
TICK_WAIT_LIMIT = 0.05
|
|
54
|
+
# The pause between stamps.
|
|
55
|
+
TICK_RETRY_DELAY = 0.0005
|
|
56
|
+
private_constant :TICK_RETRY_DELAY
|
|
57
|
+
|
|
58
|
+
# Takes the mark, or nil when the slot must not be written: no stamp can be written, it is not on the
|
|
59
|
+
# project's filesystem, or the snapshot fingerprint the run was given no longer describes the tree.
|
|
60
|
+
#
|
|
61
|
+
# The fingerprint is recomputed AFTER the stamp: the caller computed it earlier, and a lockfile or a
|
|
62
|
+
# signature file under the configured or auto-detected `sig/` roots, or a `pre_eval:` file, changed in
|
|
63
|
+
# between would leave the snapshot the run restores keyed by one tree
|
|
64
|
+
# and the run reading another. From the stamp on, the lockfiles are the guard's to watch ({#admits?}), which
|
|
65
|
+
# is also what lets the slot's key, whose only file inputs they are, be computed when the run ends. The
|
|
66
|
+
# opted-in gems' resolved versions, the key's one input that is neither a file nor the configuration (issue
|
|
67
|
+
# #1814), are read at the mark and watched the same way.
|
|
68
|
+
#
|
|
69
|
+
# The mark also records which of the paths an existence row can name are present: the lockfiles, the
|
|
70
|
+
# analysis roots, the `pre_eval:` entries and the signature roots.
|
|
71
|
+
def self.start(configuration:, roots:, cache_root:, fingerprint:)
|
|
72
|
+
return nil if Gem.win_platform?
|
|
73
|
+
|
|
74
|
+
started_ns, device = stamp(cache_root)
|
|
75
|
+
return nil if started_ns.nil? || device_of(Dir.pwd) != device
|
|
76
|
+
return nil unless Cache::IncrementalSnapshot.fingerprint(configuration: configuration, roots: roots) ==
|
|
77
|
+
fingerprint
|
|
78
|
+
|
|
79
|
+
lockfiles = lockfile_paths(configuration)
|
|
80
|
+
named = Array(roots) + configuration.pre_eval + (configuration.signature_paths || ["sig"])
|
|
81
|
+
presence = (lockfiles + named.map { |path| File.absolute_path(path.to_s) }).to_h do |path|
|
|
82
|
+
[path, File.exist?(path)]
|
|
83
|
+
end
|
|
84
|
+
rows = dependency_rows(configuration)
|
|
85
|
+
return nil if rows.nil?
|
|
86
|
+
|
|
87
|
+
new(started_ns: started_ns, device: device, lockfiles: lockfiles, presence: presence,
|
|
88
|
+
stamp_dir: stamp_dir(cache_root), configuration: configuration, dependency_rows: rows)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Issue #1814 — the opted-in gems' `(name, version, mode)` rows as the key carries them; nil when they cannot
|
|
92
|
+
# be resolved, which takes no mark (the key could not be computed either).
|
|
93
|
+
def self.dependency_rows(configuration)
|
|
94
|
+
DependencySourceInference::GemResolver.dependency_entries(configuration.dependencies)
|
|
95
|
+
rescue StandardError
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
private_class_method :dependency_rows
|
|
99
|
+
|
|
100
|
+
# The mark: the change time and device of the first stamp whose change time is later than that of a
|
|
101
|
+
# stamp taken before it, on the same device; nil when none is written within {TICK_WAIT_LIMIT}.
|
|
102
|
+
def self.stamp(cache_root)
|
|
103
|
+
dir = stamp_dir(cache_root)
|
|
104
|
+
FileUtils.mkdir_p(dir)
|
|
105
|
+
first_ns, device = write_stamp(dir)
|
|
106
|
+
# A change time on a whole second is a filesystem that ticks by the second: waiting for it would burn the
|
|
107
|
+
# whole bound. One that ticks finer lands there once in a billion stamps, and declining is safe.
|
|
108
|
+
return nil if first_ns.nil? || (first_ns % 1_000_000_000).zero?
|
|
109
|
+
|
|
110
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + TICK_WAIT_LIMIT
|
|
111
|
+
loop do
|
|
112
|
+
ns, dev = write_stamp(dir)
|
|
113
|
+
return nil if ns.nil? || dev != device
|
|
114
|
+
return [ns, device] if ns > first_ns
|
|
115
|
+
return nil if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
116
|
+
|
|
117
|
+
sleep(TICK_RETRY_DELAY)
|
|
118
|
+
end
|
|
119
|
+
rescue SystemCallError, IOError
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
private_class_method :stamp
|
|
123
|
+
|
|
124
|
+
def self.stamp_dir(cache_root)
|
|
125
|
+
File.join(cache_root.to_s, STAMP_DIR)
|
|
126
|
+
end
|
|
127
|
+
private_class_method :stamp_dir
|
|
128
|
+
|
|
129
|
+
# One stamp's change time and device; the file is removed again.
|
|
130
|
+
def self.write_stamp(dir)
|
|
131
|
+
path = File.join(dir, "run-#{Process.pid}-#{SecureRandom.hex(4)}.stamp")
|
|
132
|
+
File.write(path, "")
|
|
133
|
+
stat = File.stat(path)
|
|
134
|
+
[Cache::FileDigest.ns_of(stat.ctime), stat.dev]
|
|
135
|
+
rescue SystemCallError, IOError
|
|
136
|
+
nil
|
|
137
|
+
ensure
|
|
138
|
+
FileUtils.rm_f(path) if path
|
|
139
|
+
end
|
|
140
|
+
private_class_method :write_stamp
|
|
141
|
+
|
|
142
|
+
def self.device_of(path)
|
|
143
|
+
File.stat(path).dev
|
|
144
|
+
rescue SystemCallError
|
|
145
|
+
nil
|
|
146
|
+
end
|
|
147
|
+
private_class_method :device_of
|
|
148
|
+
|
|
149
|
+
# Every lockfile the key or the snapshot fingerprint may digest, present or not: a lockfile that appears
|
|
150
|
+
# while the run reads moves the key as surely as one rewritten, and one rewritten and restored digests as it
|
|
151
|
+
# did while the run may have read the other bytes.
|
|
152
|
+
def self.lockfile_paths(configuration)
|
|
153
|
+
configured = [
|
|
154
|
+
[configuration.bundler_lockfile, Environment::LockfileResolver],
|
|
155
|
+
[configuration.rbs_collection_lockfile, Environment::RbsCollectionDiscovery]
|
|
156
|
+
].filter_map { |path, resolver| resolver.configured_lockfile_path(path) if path }
|
|
157
|
+
(configured + %w[Gemfile.lock rbs_collection.lock.yaml].map { |path| File.absolute_path(path) }).uniq
|
|
158
|
+
end
|
|
159
|
+
private_class_method :lockfile_paths
|
|
160
|
+
|
|
161
|
+
def initialize(started_ns:, device:, lockfiles:, presence: {}, stamp_dir: nil, configuration: nil,
|
|
162
|
+
dependency_rows: [])
|
|
163
|
+
@stamp_dir = stamp_dir
|
|
164
|
+
@started_ns = started_ns
|
|
165
|
+
@device = device
|
|
166
|
+
@lockfiles = lockfiles
|
|
167
|
+
@presence = presence
|
|
168
|
+
@configuration = configuration
|
|
169
|
+
@dependency_rows = dependency_rows
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# True when nothing the guard watches moved since the mark:
|
|
173
|
+
#
|
|
174
|
+
# - no lockfile the key or the fingerprint digests was written, created or removed, and no opted-in gem
|
|
175
|
+
# resolves to another version than at the mark (issue #1814);
|
|
176
|
+
# - no file `rows` or `pinned` names changed. A content row's file changed if its change time moved. An
|
|
177
|
+
# existence row asks only whether its path is there, so a path the mark recorded changed if it came or
|
|
178
|
+
# went, and an editor's lock file created and removed beside it, which moves its directory's change
|
|
179
|
+
# time, does not count. That needs no clock, so it holds on any filesystem; it cannot see a path that
|
|
180
|
+
# comes and goes again within the run. Any other existence row falls back to change times: its own when
|
|
181
|
+
# present, its nearest existing ancestor's when absent;
|
|
182
|
+
# - no directory a glob row lists gained or lost an entry, nor, for a stat-mode glob, did any file it
|
|
183
|
+
# matches change.
|
|
184
|
+
#
|
|
185
|
+
# A path on another filesystem than the mark refuses the write when it is one of `rows`, and is passed
|
|
186
|
+
# over when it is one of `pinned`: the rows the key pins (the engine's own signature files, a gem's
|
|
187
|
+
# signatures, which the engine slot and the lockfiles identify) live wherever the engine and the gems are
|
|
188
|
+
# installed — a container image's layer, a Nix store — and refusing them would turn the fast path off for
|
|
189
|
+
# every such installation. On the mark's filesystem they are checked like any other row.
|
|
190
|
+
#
|
|
191
|
+
# A content row whose file is gone passes: the row is stale already, and validation says so.
|
|
192
|
+
#
|
|
193
|
+
# @param rows — a {Cache::Descriptor} of the rows the key does not pin
|
|
194
|
+
# @param pinned — a {Cache::Descriptor} of the rows it does
|
|
195
|
+
def admits?(rows, pinned: Cache::Descriptor.new)
|
|
196
|
+
!clock_stepped_back? && @lockfiles.none? { |path| lockfile_changed?(path) } && dependency_rows_unchanged? &&
|
|
197
|
+
files_unchanged?(rows.files, strict: true) && globs_unchanged?(rows.globs, strict: true) &&
|
|
198
|
+
files_unchanged?(pinned.files, strict: false) && globs_unchanged?(pinned.globs, strict: false)
|
|
199
|
+
rescue StandardError
|
|
200
|
+
false
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# `descriptor` with every `:stat` row's recording instant no later than the mark. A row recorded as the run
|
|
204
|
+
# read carries the instant the run began, and validation trusts a stat tuple whose mtime is before it. On a
|
|
205
|
+
# coarse clock a save that lands after the read, in the tick the file's mtime already has and with the same
|
|
206
|
+
# size, leaves the tuple unmoved, so it would be trusted. With the instant at the mark, every row whose mtime is
|
|
207
|
+
# at or after the mark is racy, and validation re-hashes it.
|
|
208
|
+
def mark_racy(descriptor)
|
|
209
|
+
Cache::Descriptor.new(
|
|
210
|
+
files: descriptor.files.map { |entry| entry.with_recording_instant_at_most(@started_ns) },
|
|
211
|
+
gems: descriptor.gems, plugins: descriptor.plugins, configs: descriptor.configs,
|
|
212
|
+
dependencies: descriptor.dependencies, globs: descriptor.globs
|
|
213
|
+
)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
private
|
|
217
|
+
|
|
218
|
+
# Issue #1814 — the opted-in gems resolve to the versions they did at the mark. A guard built without a
|
|
219
|
+
# configuration (the specs' bare constructor) watches none.
|
|
220
|
+
def dependency_rows_unchanged?
|
|
221
|
+
@configuration.nil? || self.class.send(:dependency_rows, @configuration) == @dependency_rows
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# One more stamp: a change time earlier than the mark means the clock stepped back since it was taken, so
|
|
225
|
+
# a save since then may carry a change time the guard reads as before it. A stamp that cannot be written,
|
|
226
|
+
# or lands on another device, cannot vouch for the clock either.
|
|
227
|
+
def clock_stepped_back?
|
|
228
|
+
return false if @stamp_dir.nil?
|
|
229
|
+
|
|
230
|
+
ns, device = self.class.__send__(:write_stamp, @stamp_dir)
|
|
231
|
+
ns.nil? || device != @device || ns < @started_ns
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# An existence row and a content row for one path are different questions (the first asks only whether it
|
|
235
|
+
# is there, the second whether it changed), so the row that comes first must not hide the other.
|
|
236
|
+
def files_unchanged?(entries, strict:)
|
|
237
|
+
seen = Set.new
|
|
238
|
+
entries.none? do |entry|
|
|
239
|
+
next false unless seen.add?([entry.path, entry.comparator == :exists])
|
|
240
|
+
|
|
241
|
+
if entry.comparator == :exists
|
|
242
|
+
existence_changed?(entry.path, strict: strict)
|
|
243
|
+
else
|
|
244
|
+
changed?(entry.path, strict: strict)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def globs_unchanged?(entries, strict:)
|
|
250
|
+
entries.none? do |entry|
|
|
251
|
+
listed = Dir.glob(File.join(entry.root, File.dirname(entry.pattern), ""))
|
|
252
|
+
matched = entry.mode == :stat ? Dir.glob(File.join(entry.root, entry.pattern)) : []
|
|
253
|
+
(listed + matched).any? { |path| changed?(path, strict: strict) }
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# A lockfile is read for its bytes: one absent at the mark must still be absent, one present must still be
|
|
258
|
+
# there with its change time before the mark.
|
|
259
|
+
def lockfile_changed?(path)
|
|
260
|
+
return File.exist?(path) unless @presence[path]
|
|
261
|
+
|
|
262
|
+
changed?(path, strict: true, missing: true)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def existence_changed?(path, strict:)
|
|
266
|
+
absolute = File.absolute_path(path)
|
|
267
|
+
return File.exist?(absolute) != @presence[absolute] if @presence.key?(absolute)
|
|
268
|
+
|
|
269
|
+
nearest = absolute
|
|
270
|
+
nearest = File.dirname(nearest) until File.exist?(nearest) || File.dirname(nearest) == nearest
|
|
271
|
+
moved?(File.stat(nearest), strict: strict)
|
|
272
|
+
rescue SystemCallError
|
|
273
|
+
true
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def changed?(path, strict:, missing: false)
|
|
277
|
+
moved?(File.stat(path), strict: strict)
|
|
278
|
+
rescue SystemCallError
|
|
279
|
+
missing
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# A change time is comparable with the mark only on the stamp's filesystem.
|
|
283
|
+
def moved?(stat, strict:)
|
|
284
|
+
return strict if stat.dev != @device
|
|
285
|
+
|
|
286
|
+
Cache::FileDigest.ns_of(stat.ctime) >= @started_ns
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "diagnostic"
|
|
4
|
+
require_relative "path_expansion"
|
|
5
|
+
require_relative "result"
|
|
6
|
+
require_relative "run_cache_key"
|
|
7
|
+
require_relative "../cache/descriptor"
|
|
8
|
+
require_relative "../cache/file_digest"
|
|
9
|
+
require_relative "../cache/store"
|
|
10
|
+
|
|
11
|
+
module Rigor
|
|
12
|
+
module Analysis
|
|
13
|
+
# ADR-45 WD2 (#1507) — the run-result slot `rigor check --incremental` writes, and the engine-free probe that
|
|
14
|
+
# serves a null run from it. The writer is {IncrementalSession#run_incremental}; the reader is the CLI, before
|
|
15
|
+
# it loads the inference engine. Both go through this module, so the key, the producer id and the stored shape
|
|
16
|
+
# have one definition.
|
|
17
|
+
#
|
|
18
|
+
# It is ADR-45's record-and-validate slot with a second writer. The value is the diagnostics the session's run
|
|
19
|
+
# printed (before the baseline filter, which the CLI applies to every answer), and the dependency descriptor
|
|
20
|
+
# validates everything that answer was computed from:
|
|
21
|
+
#
|
|
22
|
+
# - one `:stat` row per analysed file, carrying the digest the SESSION holds for it — the bytes its cached rows
|
|
23
|
+
# were computed from — rather than a re-digest taken after the run;
|
|
24
|
+
# - the rows the run re-read itself ({Runner#incremental_slot_rows}' `observed` and `derived` rows);
|
|
25
|
+
# - the chain the {Entry} carries forward: the rows the last full run recorded for the inputs the incremental
|
|
26
|
+
# path does not re-derive on a recheck, and the plugin reads credited to each file's analysis
|
|
27
|
+
# ({IncrementalSession#write_run_slot}).
|
|
28
|
+
#
|
|
29
|
+
# A hit is at least as careful as the full incremental path — where that path would notice a change, a row
|
|
30
|
+
# notices it first — but it is not a cold run: a stale answer that path serves for a read reaching a file
|
|
31
|
+
# through a plugin's memo (#1553) the probe may serve too. ADR-45 WD2 states the guarantee.
|
|
32
|
+
#
|
|
33
|
+
# Kept apart from the plain run's `analysis.run-diagnostics` by its own producer id and by the roots entry its
|
|
34
|
+
# key adds, either enough alone, so neither probe can ever read the other's entry, and an incremental defect
|
|
35
|
+
# cannot leak into a default run.
|
|
36
|
+
#
|
|
37
|
+
# The key is the one {RunCacheProbe} reconstructs from configuration alone — the library list without a
|
|
38
|
+
# `rbs.virtual_rbs` slot, and no `template-units` slot — plus the analysis roots ({Target}). Neither left-out
|
|
39
|
+
# slot is needed. A virtual RBS buffer is a function of an analysed file's bytes (a row) and of the
|
|
40
|
+
# synthesising plugin's identity and configuration (the key's `configuration`, lockfile and engine slots). A
|
|
41
|
+
# template unit is a function of its template's bytes and of what its transform read (rows) and of the
|
|
42
|
+
# plugin's identity and the engine's (key slots), so a project whose plugins claim template globs is served
|
|
43
|
+
# too. The ADR-87 probe misses on one: the plain slot's key carries the `template-units` slot.
|
|
44
|
+
#
|
|
45
|
+
# `explain:` is always false: the session's runners never set it (#1533), and the CLI declines the probe under
|
|
46
|
+
# `--explain`.
|
|
47
|
+
module IncrementalRunSlot
|
|
48
|
+
module_function
|
|
49
|
+
|
|
50
|
+
PRODUCER_ID = "analysis.incremental-run-diagnostics"
|
|
51
|
+
|
|
52
|
+
# One live generation per analysed-path set, as for the plain slot. A writer that moves the path set discards
|
|
53
|
+
# the entry it carried from (see {IncrementalSession}), so the churn a file addition makes is bounded without
|
|
54
|
+
# a compaction pass — the incremental path never runs one.
|
|
55
|
+
GENERATION_CAP = RunCacheKey::GENERATION_CAP
|
|
56
|
+
|
|
57
|
+
# The stored value. `baseline` and `pinned` (each a {Cache::Descriptor}) and `reads` (`{path =>
|
|
58
|
+
# Cache::Descriptor}`) are the chain the next writer carries forward, kept apart from the rest of the
|
|
59
|
+
# dependency descriptor so it knows which rows are which; their entries are the same objects the descriptor
|
|
60
|
+
# holds, so `Marshal` writes them once. `baseline` and `pinned` hold the rows the last full run recorded for
|
|
61
|
+
# inputs the incremental path does not re-derive on a recheck (the signature tree, the
|
|
62
|
+
# discovered-not-analysed files, the `pre_eval:` files outside the analysed set), `pinned` the signature
|
|
63
|
+
# files the key identifies already ({Runner::BaselineRows}); `reads` the plugin reads credited to each
|
|
64
|
+
# analysed file. `snapshot` is the identity of
|
|
65
|
+
# the incremental snapshot file the writing run left behind, which the next writer compares against the one
|
|
66
|
+
# it restored before trusting the chain. `roots` are the analysis roots as the run was given them
|
|
67
|
+
# ({.as_written}): the key holds them as a set of paths, and the probe serves the entry only to a run that
|
|
68
|
+
# names them the same way, in the same order.
|
|
69
|
+
Entry = Data.define(:diagnostics, :roots, :baseline, :pinned, :reads, :snapshot)
|
|
70
|
+
|
|
71
|
+
# What a slot is keyed by: the analysed-path set and the analysis roots the run was given. The roots are
|
|
72
|
+
# not implied by the files — `rigor check --incremental lib extra` with `extra` missing analyses the same
|
|
73
|
+
# files as `rigor check --incremental lib`, and only the first reports `extra` as missing.
|
|
74
|
+
Target = Data.define(:files, :roots)
|
|
75
|
+
|
|
76
|
+
# What a probe hit hands the CLI: the answer, and the analysed-file count its banner reports.
|
|
77
|
+
Hit = Data.define(:result, :file_count)
|
|
78
|
+
|
|
79
|
+
def key(configuration:, target:)
|
|
80
|
+
base = RunCacheKey.descriptor(
|
|
81
|
+
configuration: configuration, files: target.files, explain: false,
|
|
82
|
+
rbs_config_entries: RunCacheKey.libraries_config_entries(configuration)
|
|
83
|
+
)
|
|
84
|
+
return nil if base.nil?
|
|
85
|
+
|
|
86
|
+
roots = RunCacheKey.config_entry("incremental.roots", normalize_roots(target.roots).sort.join("\n"))
|
|
87
|
+
# Issue #1814 — the opted-in gems' `(name, version, mode)` rows travel with the key, as they do in the plain
|
|
88
|
+
# probe's: the gem's walked source reaches every file's answer and no analysed file records it.
|
|
89
|
+
Cache::Descriptor.new(gems: base.gems, dependencies: base.dependencies, configs: base.configs + [roots])
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# The roots as absolute paths, which the KEY holds sorted: `lib`, `./lib` and `lib/` are one root there. A
|
|
93
|
+
# run that reorders its roots still finds the previous slot's chain (the snapshot fingerprint sorts them
|
|
94
|
+
# too), and one that respells them replaces the slot rather than adding one beside it.
|
|
95
|
+
def normalize_roots(roots)
|
|
96
|
+
Array(roots).map { |root| File.absolute_path(root.to_s) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The roots as the run was given them, which {Entry#roots} holds and {.serve} compares. A missing root is
|
|
100
|
+
# reported as written (`./extra: no such file or directory`), and `a b` lists `a`'s files before `b`'s, so
|
|
101
|
+
# neither a respelling nor a reordering may be served another's answer.
|
|
102
|
+
def as_written(roots)
|
|
103
|
+
Array(roots).map(&:to_s)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# The engine-free probe. Returns a {Hit} when the slot for this run's analysed-path set validates, or nil to
|
|
107
|
+
# decline: a miss, a stale row, an unreadable entry, a project with effect collection on (see below), or any
|
|
108
|
+
# failure at all — declining hands the run to the full path, which is always safe.
|
|
109
|
+
#
|
|
110
|
+
# Effect collection is declined here and never written by the session. Its configuration (`effects:`) is
|
|
111
|
+
# deliberately absent from `Configuration#to_h` and so from the key, and two of its rows are judged each run
|
|
112
|
+
# from state no engine-free path can rebuild (the plain probe's `envelope_lane_live?`, #428). With it off,
|
|
113
|
+
# the only effect row a run can print is `effect.annotations-unchecked`, which is a function of the
|
|
114
|
+
# signature tree and the analysed files' annotations — both validated rows — so it is served as stored.
|
|
115
|
+
def serve(configuration:, cache_root:, paths:)
|
|
116
|
+
return nil if configuration.effects_enabled?
|
|
117
|
+
|
|
118
|
+
files = PathExpansion.ruby_files(paths, configuration.exclude_patterns)
|
|
119
|
+
slot_key = key(configuration: configuration, target: Target.new(files: files, roots: paths))
|
|
120
|
+
return nil if slot_key.nil?
|
|
121
|
+
|
|
122
|
+
entry = validated_entry(configuration, cache_root, slot_key)
|
|
123
|
+
return nil unless entry.is_a?(Entry) && entry.diagnostics.is_a?(Array)
|
|
124
|
+
return nil unless entry.roots == as_written(paths)
|
|
125
|
+
|
|
126
|
+
Hit.new(result: Result.new(diagnostics: entry.diagnostics, stats: nil), file_count: files.size)
|
|
127
|
+
rescue StandardError
|
|
128
|
+
nil
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The previous slot's {Entry}, read WITHOUT validating it: it is stale by construction, since the run that
|
|
132
|
+
# is asking changed something. nil when there is no such slot, or it is not an {Entry} — the writer then
|
|
133
|
+
# skips its own write rather than guess what the files it served from cache depended on.
|
|
134
|
+
def previous_entry(store:, configuration:, target:)
|
|
135
|
+
slot_key = key(configuration: configuration, target: target)
|
|
136
|
+
return nil if slot_key.nil?
|
|
137
|
+
|
|
138
|
+
entry = store.peek_unvalidated(producer_id: PRODUCER_ID, key_descriptor: slot_key)
|
|
139
|
+
return nil unless entry.is_a?(Entry) && entry.reads.is_a?(Hash)
|
|
140
|
+
return nil unless entry.baseline.is_a?(Cache::Descriptor) && entry.pinned.is_a?(Cache::Descriptor)
|
|
141
|
+
|
|
142
|
+
entry
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Writes `entry` as the slot for `target`, validated by `dependencies` (the whole descriptor, of which the
|
|
146
|
+
# entry's chain is a part). Returns whether it wrote.
|
|
147
|
+
def write(store:, configuration:, target:, entry:, dependencies:)
|
|
148
|
+
slot_key = key(configuration: configuration, target: target)
|
|
149
|
+
return false if slot_key.nil?
|
|
150
|
+
|
|
151
|
+
store.store_validated(
|
|
152
|
+
producer_id: PRODUCER_ID, key_descriptor: slot_key, generation_cap: GENERATION_CAP,
|
|
153
|
+
value: entry, dependencies: dependencies
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def discard(store:, configuration:, target:)
|
|
158
|
+
slot_key = key(configuration: configuration, target: target)
|
|
159
|
+
slot_key && store.discard(producer_id: PRODUCER_ID, key_descriptor: slot_key)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Validation runs in its own per-run digest scope, with the configuration's `cache.validation` mode, exactly
|
|
163
|
+
# as the plain probe's does.
|
|
164
|
+
def validated_entry(configuration, cache_root, slot_key)
|
|
165
|
+
store = Cache::Store.new(root: cache_root, max_bytes: configuration.cache_max_bytes)
|
|
166
|
+
Cache::FileDigest.with_run(strict: configuration.cache_validation_strict?) do
|
|
167
|
+
store.peek_validated(producer_id: PRODUCER_ID, key_descriptor: slot_key)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
private_class_method :validated_entry
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|