Module: Carson::Runtime::Audit

Included in:
Carson::Runtime
Defined in:
lib/carson/runtime/audit.rb

Instance Method Summary collapse

Instance Method Details

#audit!(json_output: false) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/carson/runtime/audit.rb', line 9

def audit!( json_output: false )
	fingerprint_status = block_if_outsider_fingerprints!
	return fingerprint_status unless fingerprint_status.nil?
	unless head_exists?
		if json_output
			out.puts JSON.pretty_generate( { command: "audit", status: "skipped", reason: "no commits yet", exit_code: EXIT_OK } )
		else
			puts_line "No commits yet — audit skipped for initial commit."
		end
		return EXIT_OK
	end
	audit_state = "ok"
	audit_concise_problems = []
	puts_verbose ""
	puts_verbose "[Repository]"
	puts_verbose "root: #{repo_root}"
	puts_verbose "current_branch: #{current_branch}"
	puts_verbose ""
	puts_verbose "[Working Tree]"
	puts_verbose git_capture!( "status", "--short", "--branch" ).strip
	puts_verbose ""
	puts_verbose "[Hooks]"
	hooks_ok = hooks_health_report
	hooks_status = hooks_ok ? "ok" : "mismatch"
	unless hooks_ok
		audit_state = "block"
		audit_concise_problems << "Hooks: mismatch — run carson refresh."
	end
	puts_verbose ""
	puts_verbose "[Main Sync Status]"
	ahead_count, behind_count, main_error = main_sync_counts
	main_sync = { ahead: 0, behind: 0, status: "ok" }
	if main_error
		puts_verbose "main_vs_remote_main: unknown"
		puts_verbose "WARN: unable to calculate main sync status (#{main_error})."
		audit_state = "attention" if audit_state == "ok"
		audit_concise_problems << "Main sync: unable to determine — check remote connectivity."
		main_sync = { ahead: 0, behind: 0, status: "unknown", error: main_error }
	elsif ahead_count.positive?
		puts_verbose "main_vs_remote_main_ahead: #{ahead_count}"
		puts_verbose "main_vs_remote_main_behind: #{behind_count}"
		puts_verbose "ACTION: local #{config.main_branch} is ahead of #{config.git_remote}/#{config.main_branch} by #{ahead_count} commit#{plural_suffix( count: ahead_count )}; reset local drift before commit/push workflows."
		audit_state = "block"
		audit_concise_problems << "Main sync (#{config.git_remote}): ahead by #{ahead_count} — git fetch #{config.git_remote}, or carson setup to switch remote."
		main_sync = { ahead: ahead_count, behind: behind_count, status: "ahead" }
	elsif behind_count.positive?
		puts_verbose "main_vs_remote_main_ahead: #{ahead_count}"
		puts_verbose "main_vs_remote_main_behind: #{behind_count}"
		puts_verbose "ACTION: local #{config.main_branch} is behind #{config.git_remote}/#{config.main_branch} by #{behind_count} commit#{plural_suffix( count: behind_count )}; run carson sync."
		audit_state = "attention" if audit_state == "ok"
		audit_concise_problems << "Main sync (#{config.git_remote}): behind by #{behind_count} — run carson sync."
		main_sync = { ahead: ahead_count, behind: behind_count, status: "behind" }
	else
		puts_verbose "main_vs_remote_main_ahead: 0"
		puts_verbose "main_vs_remote_main_behind: 0"
		puts_verbose "ACTION: local #{config.main_branch} is in sync with #{config.git_remote}/#{config.main_branch}."
	end
	puts_verbose ""
	puts_verbose "[PR and Required Checks (gh)]"
	monitor_report = pr_and_check_report
	audit_state = "attention" if audit_state == "ok" && !%w[ok skipped].include?( monitor_report.fetch( :status ) )
	if monitor_report.fetch( :status ) == "attention"
		checks = monitor_report.fetch( :checks )
		fail_n = checks.fetch( :failing_count )
		pend_n = checks.fetch( :pending_count )
		total = checks.fetch( :required_total )
		fail_names = checks.fetch( :failing ).map { |e| e.fetch( :name ) }.join( ", " )
		if fail_n.positive? && pend_n.positive?
			audit_concise_problems << "Checks: #{fail_n} failing (#{fail_names}), #{pend_n} pending of #{total} required."
		elsif fail_n.positive?
			audit_concise_problems << "Checks: #{fail_n} of #{total} failing (#{fail_names})."
		elsif pend_n.positive?
			audit_concise_problems << "Checks: pending (#{total - pend_n} of #{total} complete)."
		end
	end
	puts_verbose ""
	puts_verbose "[Default Branch CI Baseline (gh)]"
	default_branch_baseline = default_branch_ci_baseline_report
	audit_state = "attention" if audit_state == "ok" && !%w[ok skipped].include?( default_branch_baseline.fetch( :status ) )
	baseline_st = default_branch_baseline.fetch( :status )
	if baseline_st == "block"
		parts = []
		if default_branch_baseline.fetch( :failing_count ).positive?
			names = default_branch_baseline.fetch( :failing ).map { |e| e.fetch( :name ) }.join( ", " )
			parts << "#{default_branch_baseline.fetch( :failing_count )} failing (#{names})"
		end
		if default_branch_baseline.fetch( :pending_count ).positive?
			names = default_branch_baseline.fetch( :pending ).map { |e| e.fetch( :name ) }.join( ", " )
			parts << "#{default_branch_baseline.fetch( :pending_count )} pending (#{names})"
		end
		parts << "no check-runs for active workflows" if default_branch_baseline.fetch( :no_check_evidence )
		audit_concise_problems << "Baseline (#{default_branch_baseline.fetch( :default_branch, config.main_branch )}): #{parts.join( ', ' )} — fix before merge."
	elsif baseline_st == "attention"
		parts = []
		if default_branch_baseline.fetch( :advisory_failing_count ).positive?
			names = default_branch_baseline.fetch( :advisory_failing ).map { |e| e.fetch( :name ) }.join( ", " )
			parts << "#{default_branch_baseline.fetch( :advisory_failing_count )} advisory failing (#{names})"
		end
		if default_branch_baseline.fetch( :advisory_pending_count ).positive?
			names = default_branch_baseline.fetch( :advisory_pending ).map { |e| e.fetch( :name ) }.join( ", " )
			parts << "#{default_branch_baseline.fetch( :advisory_pending_count )} advisory pending (#{names})"
		end
		audit_concise_problems << "Baseline (#{default_branch_baseline.fetch( :default_branch, config.main_branch )}): #{parts.join( ', ' )}."
	end
	if config.template_canonical.nil? || config.template_canonical.to_s.empty?
		puts_verbose ""
		puts_verbose "[Canonical Templates]"
		puts_verbose "HINT: canonical templates not configured — run carson setup to enable."
	end
		write_and_print_pr_monitor_report(
			report: monitor_report.merge(
				default_branch_baseline: default_branch_baseline,
				audit_status: audit_state
			)
		)
	exit_code = audit_state == "block" ? EXIT_BLOCK : EXIT_OK

	if json_output
		result = {
			command: "audit",
			status: audit_state,
			branch: current_branch,
			hooks: { status: hooks_status },
			main_sync: main_sync,
			pr: monitor_report[ :pr ],
			checks: monitor_report.fetch( :checks ),
			baseline: {
				status: default_branch_baseline.fetch( :status ),
				repository: default_branch_baseline[ :repository ],
				failing_count: default_branch_baseline.fetch( :failing_count ),
				pending_count: default_branch_baseline.fetch( :pending_count ),
				advisory_failing_count: default_branch_baseline.fetch( :advisory_failing_count ),
				advisory_pending_count: default_branch_baseline.fetch( :advisory_pending_count )
			},
			problems: audit_concise_problems,
			exit_code: exit_code
		}
		out.puts JSON.pretty_generate( result )
	else
		puts_verbose ""
		puts_verbose "[Audit Result]"
		puts_verbose "status: #{audit_state}"
		puts_verbose( audit_state == "block" ? "ACTION: local policy block must be resolved before commit/push." : "ACTION: no local hard block detected." )
		unless verbose?
			audit_concise_problems.each { |problem| puts_line problem }
			puts_line "Audit: #{audit_state}"
		end
	end
	exit_code
end

#audit_all!Object

Runs audit across all governed repositories.



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/carson/runtime/audit.rb', line 161

def audit_all!
	repos = config.govern_repos
	if repos.empty?
		puts_line "No governed repositories configured."
		puts_line "  Run carson onboard in each repo to register."
		return EXIT_ERROR
	end

	puts_line ""
	puts_line "Audit all (#{repos.length} repo#{plural_suffix( count: repos.length )})"
	passed = 0
	blocked = 0
	failed = 0

	repos.each do |repo_path|
		repo_name = File.basename( repo_path )
		unless Dir.exist?( repo_path )
			puts_line "#{repo_name}: FAIL (path not found)"
			failed += 1
			next
		end

		begin
			rt = build_scoped_runtime( repo_path: repo_path )
			status = rt.audit!
			case status
			when EXIT_OK
				puts_line "#{repo_name}: ok" unless verbose?
				passed += 1
			when EXIT_BLOCK
				puts_line "#{repo_name}: BLOCK" unless verbose?
				blocked += 1
			else
				puts_line "#{repo_name}: FAIL" unless verbose?
				failed += 1
			end
		rescue StandardError => e
			puts_line "#{repo_name}: FAIL (#{e.message})"
			failed += 1
		end
	end

	puts_line ""
	puts_line "Audit all complete: #{passed} ok, #{blocked} blocked, #{failed} failed."
	blocked.zero? && failed.zero? ? EXIT_OK : EXIT_BLOCK
end