necropsy 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +33 -0
  3. data/CHANGELOG.md +44 -0
  4. data/MEASUREMENTS.md +112 -0
  5. data/README.md +233 -16
  6. data/Rakefile +3 -1
  7. data/bench/README.md +92 -0
  8. data/bench/audit.rb +113 -0
  9. data/bench/audits/0.2.1/audit.json +501 -0
  10. data/bench/audits/0.2.1/audit.md +55 -0
  11. data/bench/audits/0.2.1/baseline_performance.yml +16 -0
  12. data/bench/audits/0.2.1/config.yml +42 -0
  13. data/bench/audits/0.2.1/review.yml +56 -0
  14. data/bench/audits/0.2.1/review_queue.yml +3651 -0
  15. data/bench/corpora/v1/README.md +30 -0
  16. data/bench/corpora/v1/labels.yml +37 -0
  17. data/bench/corpora/v1/manifest.yml +63 -0
  18. data/bench/corpora/v1/rubocop.necropsy.yml +7 -0
  19. data/bench/corpora/v1/self.necropsy.yml +8 -0
  20. data/bench/corpora/v1/tools/debride.yml +42 -0
  21. data/bench/corpora/v1/tools/spoom.yml +12 -0
  22. data/bench/corpora/v1/tools/type_aware.yml +11 -0
  23. data/bench/golden/v1/candidate_union.json +21918 -0
  24. data/bench/golden/v1/metadata.json +12 -0
  25. data/bench/golden/v1/reports/dynamic_evidence.json +146 -0
  26. data/bench/golden/v1/reports/plain_ruby.json +135 -0
  27. data/bench/golden/v1/reports/rails.json +176 -0
  28. data/bench/golden/v1/reports/rubocop_1_75_0.json +26848 -0
  29. data/bench/golden/v1/reports/self.json +3449 -0
  30. data/bench/review_queue.rb +35 -0
  31. data/bench/run.rb +31 -0
  32. data/bench/schema/candidate-union-v1.schema.json +70 -0
  33. data/docs/impv_implementation_matrix.md +179 -0
  34. data/docs/migrations/0.2.1.md +57 -0
  35. data/docs/migrations/0.3.0.md +207 -0
  36. data/docs/migrations/0.4.0.md +13 -0
  37. data/docs/necropsy_performance_adr.md +43 -0
  38. data/docs/necropsy_scope_decisions.md +58 -0
  39. data/docs/necropsy_type_facts_adr.md +22 -0
  40. data/gemfiles/prism_min.gemfile +9 -0
  41. data/gemfiles/prism_min.gemfile.lock +50 -0
  42. data/lib/necropsy/analyzer.rb +121 -2
  43. data/lib/necropsy/analyzers/dynamic/coverage_collector.rb +81 -19
  44. data/lib/necropsy/analyzers/dynamic/coverage_importer.rb +104 -10
  45. data/lib/necropsy/analyzers/dynamic/coverband_importer.rb +74 -250
  46. data/lib/necropsy/analyzers/dynamic/coverband_payload_set.rb +149 -0
  47. data/lib/necropsy/analyzers/dynamic/observation_policy.rb +96 -0
  48. data/lib/necropsy/analyzers/dynamic/redis_input_limits.rb +121 -0
  49. data/lib/necropsy/analyzers/dynamic/redis_nonblocking_io.rb +94 -0
  50. data/lib/necropsy/analyzers/dynamic/redis_payload_loader.rb +165 -0
  51. data/lib/necropsy/analyzers/dynamic/redis_transport.rb +217 -0
  52. data/lib/necropsy/analyzers/dynamic/runtime_reference.rb +96 -0
  53. data/lib/necropsy/analyzers/dynamic/trace_point_collector.rb +225 -20
  54. data/lib/necropsy/analyzers/dynamic/trace_point_importer.rb +3 -1
  55. data/lib/necropsy/analyzers/legacy_result_adapter.rb +226 -0
  56. data/lib/necropsy/analyzers/static/cha.rb +33 -69
  57. data/lib/necropsy/analyzers/static/name_resolution.rb +151 -21
  58. data/lib/necropsy/analyzers/static/rta.rb +249 -25
  59. data/lib/necropsy/ast_scanner/call_recording.rb +275 -0
  60. data/lib/necropsy/ast_scanner/call_site_creation.rb +54 -0
  61. data/lib/necropsy/ast_scanner/definition_creation.rb +43 -0
  62. data/lib/necropsy/ast_scanner/dsl_macros.rb +532 -0
  63. data/lib/necropsy/ast_scanner/method_definitions.rb +358 -0
  64. data/lib/necropsy/ast_scanner/references.rb +115 -0
  65. data/lib/necropsy/ast_scanner/ruby_semantics.rb +263 -0
  66. data/lib/necropsy/ast_scanner/traversal.rb +347 -0
  67. data/lib/necropsy/ast_scanner/value_definitions.rb +78 -0
  68. data/lib/necropsy/ast_scanner.rb +89 -564
  69. data/lib/necropsy/bench/candidate_union.rb +555 -0
  70. data/lib/necropsy/bench/claim_gate.rb +112 -0
  71. data/lib/necropsy/bench/evaluator.rb +356 -24
  72. data/lib/necropsy/bench/finding_facts.rb +152 -0
  73. data/lib/necropsy/bench/precision_gate.rb +144 -0
  74. data/lib/necropsy/bench/release_audit/adversarial_runner.rb +56 -0
  75. data/lib/necropsy/bench/release_audit/artifact_writer.rb +112 -0
  76. data/lib/necropsy/bench/release_audit/config_validator.rb +112 -0
  77. data/lib/necropsy/bench/release_audit/git_snapshot.rb +36 -0
  78. data/lib/necropsy/bench/release_audit/performance_gate.rb +165 -0
  79. data/lib/necropsy/bench/release_audit/run_provenance.rb +133 -0
  80. data/lib/necropsy/bench/release_audit.rb +360 -0
  81. data/lib/necropsy/bench/report_normalizer.rb +140 -0
  82. data/lib/necropsy/bench/review_queue.rb +154 -0
  83. data/lib/necropsy/bench/safety_mutation_harness.rb +59 -0
  84. data/lib/necropsy/bench/seed_runner.rb +408 -0
  85. data/lib/necropsy/bounded_canonicalizer.rb +218 -0
  86. data/lib/necropsy/cache/scan_cache.rb +94 -18
  87. data/lib/necropsy/call_site_identity.rb +54 -0
  88. data/lib/necropsy/cli.rb +296 -53
  89. data/lib/necropsy/clock.rb +40 -0
  90. data/lib/necropsy/confidence/scorer.rb +181 -58
  91. data/lib/necropsy/configuration.rb +339 -18
  92. data/lib/necropsy/convention_rules.rb +138 -0
  93. data/lib/necropsy/definition_identity/canonical_digest.rb +278 -0
  94. data/lib/necropsy/definition_identity.rb +37 -0
  95. data/lib/necropsy/diagnostics.rb +342 -0
  96. data/lib/necropsy/embedded_ruby.rb +55 -0
  97. data/lib/necropsy/entry_points/plain.rb +125 -7
  98. data/lib/necropsy/entry_points/rails.rb +422 -105
  99. data/lib/necropsy/entry_points/test.rb +6 -1
  100. data/lib/necropsy/flow_interpreter.rb +460 -0
  101. data/lib/necropsy/graph/blocker_matching.rb +338 -0
  102. data/lib/necropsy/graph/call_graph.rb +1234 -62
  103. data/lib/necropsy/graph/definition_index.rb +149 -0
  104. data/lib/necropsy/graph/dynamic_evidence_tracking.rb +206 -0
  105. data/lib/necropsy/graph/evidence_store.rb +213 -0
  106. data/lib/necropsy/graph/resolution_store.rb +497 -0
  107. data/lib/necropsy/graph_self_check.rb +79 -0
  108. data/lib/necropsy/guardrail/baseline.rb +359 -13
  109. data/lib/necropsy/guardrail/diff.rb +5 -2
  110. data/lib/necropsy/guardrail/quarantine.rb +101 -10
  111. data/lib/necropsy/load_graph.rb +206 -0
  112. data/lib/necropsy/models.rb +894 -21
  113. data/lib/necropsy/performance_profiler.rb +108 -0
  114. data/lib/necropsy/project.rb +361 -13
  115. data/lib/necropsy/reachability/engine.rb +80 -15
  116. data/lib/necropsy/reference_barrier.rb +458 -0
  117. data/lib/necropsy/report.rb +153 -14
  118. data/lib/necropsy/reporter.rb +441 -19
  119. data/lib/necropsy/runner.rb +258 -14
  120. data/lib/necropsy/runtime_feedback.rb +136 -0
  121. data/lib/necropsy/semantics_matrix.rb +153 -0
  122. data/lib/necropsy/trace_point_runtime.rb +19 -0
  123. data/lib/necropsy/type_facts.rb +53 -0
  124. data/lib/necropsy/version.rb +1 -1
  125. data/lib/necropsy/why_not_explanation.rb +436 -0
  126. data/lib/necropsy/why_not_renderer.rb +197 -0
  127. data/lib/necropsy/world_policy.rb +90 -0
  128. data/lib/necropsy.rb +39 -2
  129. data/schema/necropsy-report-v2.schema.json +366 -0
  130. data/script/measure.rb +20 -0
  131. metadata +106 -2
data/bench/audit.rb ADDED
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'optparse'
5
+ require 'yaml'
6
+ require_relative '../lib/necropsy'
7
+ require_relative '../lib/necropsy/bench/seed_runner'
8
+ require_relative '../lib/necropsy/bench/release_audit'
9
+ require_relative '../lib/necropsy/bench/release_audit/adversarial_runner'
10
+ require_relative '../lib/necropsy/bench/release_audit/artifact_writer'
11
+ require_relative '../lib/necropsy/bench/release_audit/config_validator'
12
+ require_relative '../lib/necropsy/bench/release_audit/git_snapshot'
13
+ require_relative '../lib/necropsy/bench/release_audit/run_provenance'
14
+
15
+ root = File.expand_path('..', __dir__)
16
+ options = {
17
+ config: File.join(__dir__, 'audits/0.2.1/config.yml'),
18
+ benchmark_output: File.join(root, 'tmp/necropsy-benchmark/v1'),
19
+ audit_output: File.join(__dir__, 'audits/0.2.1'),
20
+ run_benchmark: true,
21
+ run_adversarial: true
22
+ }
23
+
24
+ OptionParser.new do |parser|
25
+ parser.banner = 'Usage: bundle exec ruby bench/audit.rb [options]'
26
+ parser.on('--config PATH', 'Audit configuration') { |path| options[:config] = path }
27
+ parser.on('--benchmark-output DIR', 'Current benchmark output') { |path| options[:benchmark_output] = path }
28
+ parser.on('--output DIR', 'Audit artifact output') { |path| options[:audit_output] = path }
29
+ parser.on('--skip-benchmark', 'Use an existing current benchmark run') { options[:run_benchmark] = false }
30
+ parser.on('--skip-adversarial', 'Do not execute adversarial suites') { options[:run_adversarial] = false }
31
+ end.parse!
32
+
33
+ config_path = File.expand_path(options.fetch(:config), root)
34
+ config = YAML.safe_load_file(config_path, aliases: false)
35
+ Necropsy::Bench::ReleaseAudit::ConfigValidator.new(config).validate!
36
+
37
+ manifest_path = File.join(root, 'bench/corpora/v1/manifest.yml')
38
+ benchmark_output = File.expand_path(options.fetch(:benchmark_output), root)
39
+ provenance_path = File.join(benchmark_output, 'run_metadata.json')
40
+ rubocop_corpus = ENV.fetch('NECROPSY_RUBOCOP_CORPUS', '<unset>')
41
+ measurement_command = "NECROPSY_RUBOCOP_CORPUS=#{rubocop_corpus} bundle exec ruby bench/run.rb"
42
+ provenance = Necropsy::Bench::ReleaseAudit::RunProvenance.new(
43
+ root: root,
44
+ manifest_path: manifest_path,
45
+ config_path: config_path,
46
+ output_dir: benchmark_output,
47
+ command: measurement_command
48
+ )
49
+
50
+ current_provenance = if options[:run_benchmark]
51
+ source = provenance.capture_source!
52
+ summary = Necropsy::Bench::SeedRunner.new(
53
+ manifest_path: manifest_path,
54
+ output_dir: benchmark_output
55
+ ).call
56
+ provenance.complete(source, summary).tap do |payload|
57
+ provenance.write(provenance_path, payload)
58
+ end
59
+ else
60
+ provenance.load_and_validate!(provenance_path)
61
+ end
62
+
63
+ corpora = config.fetch('corpora')
64
+ current_reports = corpora.to_h do |corpus|
65
+ path = File.join(benchmark_output, 'reports', "#{corpus}.json")
66
+ [corpus, JSON.parse(File.read(path))]
67
+ end
68
+ current_summary = JSON.parse(File.read(File.join(benchmark_output, 'summary.json')))
69
+ baseline = config.fetch('baseline')
70
+ baseline_reports = Necropsy::Bench::ReleaseAudit::GitSnapshot.new(
71
+ root: root,
72
+ git_ref: baseline.fetch('git_ref'),
73
+ reports_path: baseline.fetch('reports_path')
74
+ ).reports(corpora)
75
+
76
+ load_yaml = lambda do |relative|
77
+ YAML.safe_load_file(File.expand_path(relative, root), aliases: false) || {}
78
+ end
79
+ label_entries = Array(load_yaml.call(config.fetch('labels_path'))['labels'])
80
+ labels = label_entries.to_h { |entry| [[entry.fetch('corpus'), entry.fetch('id')], entry] }
81
+ reviews = Array(load_yaml.call(config.dig('review', 'path'))['reviews'])
82
+ baseline_performance = load_yaml.call(config.dig('performance', 'baseline_path'))
83
+ adversarial_results = if options[:run_adversarial]
84
+ Necropsy::Bench::ReleaseAudit::AdversarialRunner.new(
85
+ root: root,
86
+ suites: config.fetch('adversarial_suites')
87
+ ).call
88
+ else
89
+ config.fetch('adversarial_suites').sort.map do |name, definition|
90
+ { 'name' => name, 'command' => definition.fetch('command'), 'passed' => false,
91
+ 'exit_status' => nil, 'duration_seconds' => 0.0, 'summary' => 'not run' }
92
+ end
93
+ end
94
+
95
+ audit = Necropsy::Bench::ReleaseAudit.new(
96
+ config: config,
97
+ baseline_reports: baseline_reports,
98
+ current_reports: current_reports,
99
+ current_summary: current_summary,
100
+ labels: labels,
101
+ reviews: reviews,
102
+ baseline_performance: baseline_performance,
103
+ adversarial_results: adversarial_results,
104
+ current_provenance: current_provenance
105
+ ).call
106
+ paths = Necropsy::Bench::ReleaseAudit::ArtifactWriter.new(
107
+ audit: audit,
108
+ output_dir: File.expand_path(options.fetch(:audit_output), root)
109
+ ).call
110
+ puts "release audit: #{audit.fetch('status')}"
111
+ puts "machine artifact: #{paths.fetch(:json)}"
112
+ puts "human artifact: #{paths.fetch(:markdown)}"
113
+ exit(audit.fetch('status') == 'pass' ? 0 : 1)
@@ -0,0 +1,501 @@
1
+ {
2
+ "schema_version": 1,
3
+ "release": "0.2.1",
4
+ "baseline": {
5
+ "git_ref": "d0168e881120320d28dc2f42e10f9947330fdcb7",
6
+ "reports_path": "bench/golden/v1/reports",
7
+ "reason": "d0168e881120320d28dc2f42e10f9947330fdcb7 is the first commit with the bounded evidence identity path and artifact-independent production scopes for all five required corpora."
8
+ },
9
+ "corpora": {
10
+ "dynamic_evidence": {
11
+ "baseline_metrics": {
12
+ "analysis_graph": {
13
+ "scope": "analysis",
14
+ "nodes": 6,
15
+ "definitions": 5,
16
+ "call_sites": 4,
17
+ "edges": 2
18
+ },
19
+ "findings": 4,
20
+ "actionable_candidates": 4,
21
+ "candidate_loc": 12,
22
+ "diagnostic_findings": 0
23
+ },
24
+ "current_metrics": {
25
+ "analysis_graph": {
26
+ "scope": "analysis",
27
+ "nodes": 6,
28
+ "definitions": 5,
29
+ "call_sites": 4,
30
+ "edges": 2
31
+ },
32
+ "findings": 4,
33
+ "actionable_candidates": 4,
34
+ "candidate_loc": 12,
35
+ "diagnostic_findings": 0
36
+ },
37
+ "added": [],
38
+ "removed": [],
39
+ "state_changed": [],
40
+ "confidence_changed": []
41
+ },
42
+ "plain_ruby": {
43
+ "baseline_metrics": {
44
+ "analysis_graph": {
45
+ "scope": "analysis",
46
+ "nodes": 8,
47
+ "definitions": 6,
48
+ "call_sites": 7,
49
+ "edges": 3
50
+ },
51
+ "findings": 3,
52
+ "actionable_candidates": 0,
53
+ "candidate_loc": 0,
54
+ "diagnostic_findings": 3
55
+ },
56
+ "current_metrics": {
57
+ "analysis_graph": {
58
+ "scope": "analysis",
59
+ "nodes": 8,
60
+ "definitions": 6,
61
+ "call_sites": 7,
62
+ "edges": 3
63
+ },
64
+ "findings": 3,
65
+ "actionable_candidates": 0,
66
+ "candidate_loc": 0,
67
+ "diagnostic_findings": 3
68
+ },
69
+ "added": [],
70
+ "removed": [],
71
+ "state_changed": [],
72
+ "confidence_changed": []
73
+ },
74
+ "rails": {
75
+ "baseline_metrics": {
76
+ "analysis_graph": {
77
+ "scope": "analysis",
78
+ "nodes": 32,
79
+ "definitions": 23,
80
+ "call_sites": 49,
81
+ "edges": 17
82
+ },
83
+ "findings": 4,
84
+ "actionable_candidates": 3,
85
+ "candidate_loc": 7,
86
+ "diagnostic_findings": 1
87
+ },
88
+ "current_metrics": {
89
+ "analysis_graph": {
90
+ "scope": "analysis",
91
+ "nodes": 32,
92
+ "definitions": 23,
93
+ "call_sites": 49,
94
+ "edges": 17
95
+ },
96
+ "findings": 4,
97
+ "actionable_candidates": 3,
98
+ "candidate_loc": 7,
99
+ "diagnostic_findings": 1
100
+ },
101
+ "added": [],
102
+ "removed": [],
103
+ "state_changed": [],
104
+ "confidence_changed": []
105
+ },
106
+ "rubocop_1_75_0": {
107
+ "baseline_metrics": {
108
+ "analysis_graph": {
109
+ "scope": "analysis",
110
+ "nodes": 1223,
111
+ "definitions": 1122,
112
+ "call_sites": 6199,
113
+ "edges": 1946
114
+ },
115
+ "findings": 1122,
116
+ "actionable_candidates": 547,
117
+ "candidate_loc": 3632,
118
+ "diagnostic_findings": 575
119
+ },
120
+ "current_metrics": {
121
+ "analysis_graph": {
122
+ "scope": "analysis",
123
+ "nodes": 1223,
124
+ "definitions": 1122,
125
+ "call_sites": 6199,
126
+ "edges": 1946
127
+ },
128
+ "findings": 1122,
129
+ "actionable_candidates": 547,
130
+ "candidate_loc": 3632,
131
+ "diagnostic_findings": 575
132
+ },
133
+ "added": [],
134
+ "removed": [],
135
+ "state_changed": [],
136
+ "confidence_changed": []
137
+ },
138
+ "self": {
139
+ "baseline_metrics": {
140
+ "analysis_graph": {
141
+ "scope": "analysis",
142
+ "nodes": 1975,
143
+ "definitions": 1886,
144
+ "call_sites": 15170,
145
+ "edges": 15283
146
+ },
147
+ "findings": 145,
148
+ "actionable_candidates": 82,
149
+ "candidate_loc": 621,
150
+ "diagnostic_findings": 63
151
+ },
152
+ "current_metrics": {
153
+ "analysis_graph": {
154
+ "scope": "analysis",
155
+ "nodes": 1993,
156
+ "definitions": 1902,
157
+ "call_sites": 15348,
158
+ "edges": 15519
159
+ },
160
+ "findings": 147,
161
+ "actionable_candidates": 82,
162
+ "candidate_loc": 622,
163
+ "diagnostic_findings": 65
164
+ },
165
+ "added": [
166
+ {
167
+ "id": "Necropsy::Bench::ReviewQueue#validate_options!",
168
+ "definition_id": "def:v1:0ae44a8f731da2b338b26914945b6a338b41d5eee6622efac32e93f38e10494f",
169
+ "identity": "def:v1:0ae44a8f731da2b338b26914945b6a338b41d5eee6622efac32e93f38e10494f",
170
+ "after": {
171
+ "id": "Necropsy::Bench::ReviewQueue#validate_options!",
172
+ "definition_id": "def:v1:0ae44a8f731da2b338b26914945b6a338b41d5eee6622efac32e93f38e10494f",
173
+ "path": "lib/necropsy/bench/review_queue.rb",
174
+ "line": 54,
175
+ "end_line": 57,
176
+ "loc": 4,
177
+ "state": "unreachable",
178
+ "confidence": "low",
179
+ "candidate": true,
180
+ "diagnostic": false,
181
+ "category": "unreachable",
182
+ "unknown": false,
183
+ "rule_hits": [],
184
+ "risk_flags": [],
185
+ "blocker_kinds": [],
186
+ "reasons": [
187
+ "Lowered because this node is near unresolved metaprogramming."
188
+ ]
189
+ }
190
+ },
191
+ {
192
+ "id": "Necropsy::Bench::ReviewQueue#initialize",
193
+ "definition_id": "def:v1:8bbd2d0801726c9c679a6d4936dc427c01c6b9f9ebf5dfb24c69c0185f2b90c8",
194
+ "identity": "def:v1:8bbd2d0801726c9c679a6d4936dc427c01c6b9f9ebf5dfb24c69c0185f2b90c8",
195
+ "after": {
196
+ "id": "Necropsy::Bench::ReviewQueue#initialize",
197
+ "definition_id": "def:v1:8bbd2d0801726c9c679a6d4936dc427c01c6b9f9ebf5dfb24c69c0185f2b90c8",
198
+ "path": "lib/necropsy/bench/review_queue.rb",
199
+ "line": 17,
200
+ "end_line": 24,
201
+ "loc": 8,
202
+ "state": "blocked",
203
+ "confidence": "low",
204
+ "candidate": false,
205
+ "diagnostic": true,
206
+ "category": "unknown_dispatch",
207
+ "unknown": true,
208
+ "rule_hits": [],
209
+ "risk_flags": [
210
+ "analysis_incomplete",
211
+ "public_or_protected_visibility"
212
+ ],
213
+ "blocker_kinds": [
214
+ "partial_dispatch",
215
+ "unknown_dispatch"
216
+ ],
217
+ "reasons": [
218
+ "Blocked by 552 unresolved runtime dispatches.",
219
+ "Lowered because this node is near unresolved metaprogramming."
220
+ ]
221
+ }
222
+ }
223
+ ],
224
+ "removed": [],
225
+ "state_changed": [
226
+ {
227
+ "id": "Necropsy::Bench::FindingFacts#actionable?",
228
+ "definition_id": "def:v1:2493c12409215905a9578963febb90a105e39e32b71558d9453813098d80f6c8",
229
+ "identity": "def:v1:2493c12409215905a9578963febb90a105e39e32b71558d9453813098d80f6c8",
230
+ "before": {
231
+ "id": "Necropsy::Bench::FindingFacts#actionable?",
232
+ "definition_id": "def:v1:2493c12409215905a9578963febb90a105e39e32b71558d9453813098d80f6c8",
233
+ "path": "lib/necropsy/bench/finding_facts.rb",
234
+ "line": 21,
235
+ "end_line": 23,
236
+ "loc": 3,
237
+ "state": "unreachable",
238
+ "confidence": "low",
239
+ "candidate": true,
240
+ "diagnostic": false,
241
+ "category": "unreachable",
242
+ "unknown": false,
243
+ "rule_hits": [],
244
+ "risk_flags": [],
245
+ "blocker_kinds": [],
246
+ "reasons": [
247
+ "Lowered because this node is near unresolved metaprogramming."
248
+ ]
249
+ },
250
+ "after": {
251
+ "id": "Necropsy::Bench::FindingFacts#actionable?",
252
+ "definition_id": "def:v1:2493c12409215905a9578963febb90a105e39e32b71558d9453813098d80f6c8",
253
+ "path": "lib/necropsy/bench/finding_facts.rb",
254
+ "line": 21,
255
+ "end_line": 23,
256
+ "loc": 3,
257
+ "state": "blocked",
258
+ "confidence": "low",
259
+ "candidate": false,
260
+ "diagnostic": true,
261
+ "category": "partial_dispatch",
262
+ "unknown": true,
263
+ "rule_hits": [],
264
+ "risk_flags": [
265
+ "analysis_incomplete"
266
+ ],
267
+ "blocker_kinds": [
268
+ "partial_dispatch"
269
+ ],
270
+ "reasons": [
271
+ "Blocked by 1 unresolved runtime dispatch.",
272
+ "Lowered because this node is near unresolved metaprogramming."
273
+ ]
274
+ }
275
+ }
276
+ ],
277
+ "confidence_changed": []
278
+ }
279
+ },
280
+ "new_high_candidates": [],
281
+ "review": {
282
+ "required": [],
283
+ "completed": [],
284
+ "missing": [],
285
+ "invalid": [],
286
+ "coverage": {
287
+ "rails": {
288
+ "strategy": "all",
289
+ "changes": 0,
290
+ "required": 0,
291
+ "completed": 0,
292
+ "zero_difference": true
293
+ },
294
+ "rubocop_1_75_0": {
295
+ "strategy": "stratified",
296
+ "changes": 0,
297
+ "required": 0,
298
+ "completed": 0,
299
+ "zero_difference": true
300
+ }
301
+ },
302
+ "confirmed_false_positives": []
303
+ },
304
+ "performance": {
305
+ "dynamic_evidence": {
306
+ "available": true,
307
+ "baseline_wall_time_seconds": 0.3476,
308
+ "current_wall_time_seconds": 0.146051,
309
+ "wall_time_limit_seconds": 1.0,
310
+ "baseline_rss_kb": 57728,
311
+ "current_rss_kb": 57696,
312
+ "rss_limit_kb": 123264,
313
+ "rss_kind": "current_process_rss_after_corpus",
314
+ "rss_scope": "benchmark_runner_process",
315
+ "passed": true
316
+ },
317
+ "plain_ruby": {
318
+ "available": true,
319
+ "baseline_wall_time_seconds": 0.381025,
320
+ "current_wall_time_seconds": 0.122945,
321
+ "wall_time_limit_seconds": 1.0,
322
+ "baseline_rss_kb": 59360,
323
+ "current_rss_kb": 62944,
324
+ "rss_limit_kb": 124896,
325
+ "rss_kind": "current_process_rss_after_corpus",
326
+ "rss_scope": "benchmark_runner_process",
327
+ "passed": true
328
+ },
329
+ "rails": {
330
+ "available": true,
331
+ "baseline_wall_time_seconds": 0.369907,
332
+ "current_wall_time_seconds": 0.149768,
333
+ "wall_time_limit_seconds": 1.119907,
334
+ "baseline_rss_kb": 63168,
335
+ "current_rss_kb": 65904,
336
+ "rss_limit_kb": 128704,
337
+ "rss_kind": "current_process_rss_after_corpus",
338
+ "rss_scope": "benchmark_runner_process",
339
+ "passed": true
340
+ },
341
+ "rubocop_1_75_0": {
342
+ "available": true,
343
+ "baseline_wall_time_seconds": 5.559264,
344
+ "current_wall_time_seconds": 5.50558,
345
+ "wall_time_limit_seconds": 8.338896,
346
+ "baseline_rss_kb": 349136,
347
+ "current_rss_kb": 355920,
348
+ "rss_limit_kb": 436420,
349
+ "rss_kind": "current_process_rss_after_corpus",
350
+ "rss_scope": "benchmark_runner_process",
351
+ "passed": true
352
+ },
353
+ "self": {
354
+ "available": true,
355
+ "baseline_wall_time_seconds": 22.508282,
356
+ "current_wall_time_seconds": 24.104173,
357
+ "wall_time_limit_seconds": 30.0,
358
+ "baseline_rss_kb": 1136080,
359
+ "current_rss_kb": 1175248,
360
+ "rss_limit_kb": 1300000,
361
+ "rss_kind": "current_process_rss_after_corpus",
362
+ "rss_scope": "benchmark_runner_process",
363
+ "passed": true
364
+ }
365
+ },
366
+ "precision_gate": {
367
+ "schema_version": 1,
368
+ "enforced": false,
369
+ "compatibility": "releases before 0.4 retain the safety-only release policy",
370
+ "passed": true
371
+ },
372
+ "claim_gate": {
373
+ "schema_version": 1,
374
+ "enforced": false,
375
+ "passed": true
376
+ },
377
+ "performance_provenance": {
378
+ "baseline": {
379
+ "schema_version": 1,
380
+ "captured_at": "2026-08-08T18:10:00+09:00",
381
+ "git_ref": "d0168e881120320d28dc2f42e10f9947330fdcb7",
382
+ "environment": {
383
+ "ruby": "ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]",
384
+ "os": "macOS 26.3 (25D125), Darwin 25.3.0 arm64",
385
+ "command": "NECROPSY_RUBOCOP_CORPUS=/private/tmp/necropsy-rubocop-1.75.0 bundle exec ruby bench/run.rb",
386
+ "execution": "clean worktree; escalated run so ps-based RSS measurement was available",
387
+ "rss_kind": "current_process_rss_after_corpus",
388
+ "rss_scope": "benchmark_runner_process"
389
+ }
390
+ },
391
+ "current": {
392
+ "schema_version": 1,
393
+ "git_ref": "9698ce9551f0df9a18fa09bfc0020d880fd89dc4",
394
+ "clean": true,
395
+ "manifest_sha256": "228a46b3c73635b420d5135b4b2d485e38e827fd0ae33bf5c07bf1da2ef831ac",
396
+ "audit_config_sha256": "b19a243d918b663419e7e5a671733ef41eb061ddc52eaf75658996ba3607e91e",
397
+ "environment": {
398
+ "ruby": "ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]",
399
+ "os": "macOS 26.3 (25D125), Darwin 25.3.0 arm64",
400
+ "command": "NECROPSY_RUBOCOP_CORPUS=/private/tmp/necropsy-rubocop-1.75.0 bundle exec ruby bench/run.rb",
401
+ "rss_kind": "current_process_rss_after_corpus",
402
+ "rss_scope": "benchmark_runner_process"
403
+ },
404
+ "artifacts": {
405
+ "reports/dynamic_evidence.json": "bd82a2b9fb2fae3777d81743917f2df59744798c5f58747219565436134de7ed",
406
+ "reports/plain_ruby.json": "fc939ec706e35ada3c59428856b9f8408ffc3f7468265a3c322c26c65c138169",
407
+ "reports/rails.json": "c0d2195364260ff955ae933acfb57d4e3e9697185e619d79635f70ca0f4b01fb",
408
+ "reports/rubocop_1_75_0.json": "48a136417c6b5802282720cad1f9a608a709b7a8b6e712de2ae228edd79413b5",
409
+ "reports/self.json": "dab9b43f2e78c3b35075768b953bec8a2cb79d3625bc0ad9d71005d1c3950fab",
410
+ "summary.json": "81d2e80f04dd9618ca1aaff27707a0818ec235d083dacf13c185d063eb103d5d"
411
+ }
412
+ }
413
+ },
414
+ "adversarial_suites": [
415
+ {
416
+ "name": "ambiguity",
417
+ "command": [
418
+ "bundle",
419
+ "exec",
420
+ "rspec",
421
+ "spec/necropsy/safety_invariants_spec.rb",
422
+ "spec/necropsy/reflective_blocker_visibility_spec.rb"
423
+ ],
424
+ "passed": true,
425
+ "exit_status": 0,
426
+ "duration_seconds": 0.554484,
427
+ "summary": "17 examples, 0 failures"
428
+ },
429
+ {
430
+ "name": "dynamic",
431
+ "command": [
432
+ "bundle",
433
+ "exec",
434
+ "rspec",
435
+ "spec/necropsy/analyzers/dynamic/coverage_importer_spec.rb",
436
+ "spec/necropsy/confidence/scorer_spec.rb"
437
+ ],
438
+ "passed": true,
439
+ "exit_status": 0,
440
+ "duration_seconds": 0.446656,
441
+ "summary": "24 examples, 0 failures"
442
+ },
443
+ {
444
+ "name": "parse",
445
+ "command": [
446
+ "bundle",
447
+ "exec",
448
+ "rspec",
449
+ "spec/necropsy/parse_incompleteness_spec.rb"
450
+ ],
451
+ "passed": true,
452
+ "exit_status": 0,
453
+ "duration_seconds": 0.468912,
454
+ "summary": "10 examples, 0 failures"
455
+ },
456
+ {
457
+ "name": "remote_input",
458
+ "command": [
459
+ "bundle",
460
+ "exec",
461
+ "rspec",
462
+ "spec/necropsy/analyzers/dynamic/coverband_importer_spec.rb"
463
+ ],
464
+ "passed": true,
465
+ "exit_status": 0,
466
+ "duration_seconds": 0.759857,
467
+ "summary": "28 examples, 0 failures"
468
+ }
469
+ ],
470
+ "gates": {
471
+ "new_high_reviewed": {
472
+ "passed": true,
473
+ "failures": 0
474
+ },
475
+ "new_high_false_positives": {
476
+ "passed": true,
477
+ "failures": 0
478
+ },
479
+ "new_high_unresolved": {
480
+ "passed": true,
481
+ "failures": 0
482
+ },
483
+ "difference_review": {
484
+ "passed": true,
485
+ "failures": 0
486
+ },
487
+ "review_false_positives": {
488
+ "passed": true,
489
+ "failures": 0
490
+ },
491
+ "performance": {
492
+ "passed": true,
493
+ "failures": 0
494
+ },
495
+ "adversarial": {
496
+ "passed": true,
497
+ "failures": 0
498
+ }
499
+ },
500
+ "status": "pass"
501
+ }
@@ -0,0 +1,55 @@
1
+ # 0.2.1 safety release audit
2
+
3
+ Status: **PASS**
4
+
5
+ Baseline: `d0168e881120320d28dc2f42e10f9947330fdcb7` — d0168e881120320d28dc2f42e10f9947330fdcb7 is the first commit with the bounded evidence identity path and artifact-independent production scopes for all five required corpora.
6
+
7
+ ## Candidate changes
8
+
9
+ | corpus | baseline | current | added | removed | state changed | newly high |
10
+ |---|---:|---:|---:|---:|---:|---:|
11
+ | dynamic_evidence | 4 | 4 | 0 | 0 | 0 | 0 |
12
+ | plain_ruby | 3 | 3 | 0 | 0 | 0 | 0 |
13
+ | rails | 4 | 4 | 0 | 0 | 0 | 0 |
14
+ | rubocop_1_75_0 | 1122 | 1122 | 0 | 0 | 0 | 0 |
15
+ | self | 145 | 147 | 2 | 0 | 1 | 0 |
16
+
17
+ ## Difference review
18
+
19
+ | corpus | strategy | changes | required | completed | zero difference |
20
+ |---|---|---:|---:|---:|:---:|
21
+ | rails | all | 0 | 0 | 0 | yes |
22
+ | rubocop_1_75_0 | stratified | 0 | 0 | 0 | yes |
23
+
24
+ ## Performance
25
+
26
+ | corpus | wall baseline/current/limit (s) | RSS baseline/current/limit (KiB) | pass |
27
+ |---|---:|---:|:---:|
28
+ | dynamic_evidence | 0.3476 / 0.146051 / 1.0 | 57728 / 57696 / 123264 | yes |
29
+ | plain_ruby | 0.381025 / 0.122945 / 1.0 | 59360 / 62944 / 124896 | yes |
30
+ | rails | 0.369907 / 0.149768 / 1.119907 | 63168 / 65904 / 128704 | yes |
31
+ | rubocop_1_75_0 | 5.559264 / 5.50558 / 8.338896 | 349136 / 355920 / 436420 | yes |
32
+ | self | 22.508282 / 24.104173 / 30.0 | 1136080 / 1175248 / 1300000 | yes |
33
+
34
+ ## Adversarial suites
35
+
36
+ | suite | result | summary |
37
+ |---|:---:|---|
38
+ | ambiguity | pass | 17 examples, 0 failures |
39
+ | dynamic | pass | 24 examples, 0 failures |
40
+ | parse | pass | 10 examples, 0 failures |
41
+ | remote_input | pass | 28 examples, 0 failures |
42
+
43
+ ## Release gates
44
+
45
+ | gate | result | failures |
46
+ |---|:---:|---:|
47
+ | adversarial | pass | 0 |
48
+ | difference_review | pass | 0 |
49
+ | new_high_false_positives | pass | 0 |
50
+ | new_high_reviewed | pass | 0 |
51
+ | new_high_unresolved | pass | 0 |
52
+ | performance | pass | 0 |
53
+ | review_false_positives | pass | 0 |
54
+
55
+ Reviewed RuboCop/Rails changes: 0; missing: 0.
@@ -0,0 +1,16 @@
1
+ schema_version: 1
2
+ captured_at: "2026-08-08T18:10:00+09:00"
3
+ git_ref: d0168e881120320d28dc2f42e10f9947330fdcb7
4
+ environment:
5
+ ruby: "ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [arm64-darwin24]"
6
+ os: "macOS 26.3 (25D125), Darwin 25.3.0 arm64"
7
+ command: "NECROPSY_RUBOCOP_CORPUS=/private/tmp/necropsy-rubocop-1.75.0 bundle exec ruby bench/run.rb"
8
+ execution: "clean worktree; escalated run so ps-based RSS measurement was available"
9
+ rss_kind: current_process_rss_after_corpus
10
+ rss_scope: benchmark_runner_process
11
+ corpora:
12
+ dynamic_evidence: { wall_time_seconds: 0.347600, rss_kb: 57728 }
13
+ plain_ruby: { wall_time_seconds: 0.381025, rss_kb: 59360 }
14
+ rails: { wall_time_seconds: 0.369907, rss_kb: 63168 }
15
+ rubocop_1_75_0: { wall_time_seconds: 5.559264, rss_kb: 349136 }
16
+ self: { wall_time_seconds: 22.508282, rss_kb: 1136080 }