Module: Carson::Runtime::Audit

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

Instance Method Summary collapse

Instance Method Details

#audit!Object



6
7
8
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
# File 'lib/carson/runtime/audit.rb', line 6

def audit!
	fingerprint_status = block_if_outsider_fingerprints!
	return fingerprint_status unless fingerprint_status.nil?
	unless head_exists?
		puts_line "No commits yet — audit skipped for initial commit."
		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
	unless hooks_ok
		audit_state = "block"
		audit_concise_problems << "Hooks: mismatch — run carson prepare."
	end
	puts_verbose ""
	puts_verbose "[Main Sync Status]"
	ahead_count, behind_count, main_error = main_sync_counts
	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."
	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."
	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."
	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" && monitor_report.fetch( :status ) != "ok"
	if monitor_report.fetch( :status ) == "skipped"
		audit_concise_problems << "Checks: skipped (#{monitor_report.fetch( :skip_reason )})."
	elsif 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 )
		if fail_n.positive? && pend_n.positive?
			audit_concise_problems << "Checks: #{fail_n} failing, #{pend_n} pending of #{total} required."
		elsif fail_n.positive?
			audit_concise_problems << "Checks: #{fail_n} of #{total} failing."
		elsif pend_n.positive?
			audit_concise_problems << "Checks: pending (#{total - pend_n} of #{total} complete)."
		elsif checks.fetch( :status ) == "skipped"
			audit_concise_problems << "Checks: skipped (#{checks.fetch( :skip_reason )})."
		end
	end
	puts_verbose ""
	puts_verbose "[Default Branch CI Baseline (gh)]"
	default_branch_baseline = default_branch_ci_baseline_report
	audit_state = "block" if default_branch_baseline.fetch( :status ) == "block"
	audit_state = "attention" if audit_state == "ok" && default_branch_baseline.fetch( :status ) != "ok"
	baseline_st = default_branch_baseline.fetch( :status )
	if baseline_st == "block"
		parts = []
		parts << "#{default_branch_baseline.fetch( :failing_count )} failing" if default_branch_baseline.fetch( :failing_count ).positive?
		parts << "#{default_branch_baseline.fetch( :pending_count )} pending" if default_branch_baseline.fetch( :pending_count ).positive?
		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( ', ' )} — merge blocked."
	elsif baseline_st == "attention"
		parts = []
		parts << "#{default_branch_baseline.fetch( :advisory_failing_count )} advisory failing" if default_branch_baseline.fetch( :advisory_failing_count ).positive?
		parts << "#{default_branch_baseline.fetch( :advisory_pending_count )} advisory pending" if default_branch_baseline.fetch( :advisory_pending_count ).positive?
		audit_concise_problems << "Baseline (#{default_branch_baseline.fetch( :default_branch, config.main_branch )}): #{parts.join( ', ' )}."
	elsif baseline_st == "skipped"
		audit_concise_problems << "Baseline: skipped (#{default_branch_baseline.fetch( :skip_reason )})."
	end
	scope_guard = print_scope_integrity_guard
	audit_state = "attention" if audit_state == "ok" && scope_guard.fetch( :status ) == "attention"
	if scope_guard.fetch( :status ) == "attention"
		if scope_guard.fetch( :split_required )
			audit_concise_problems << "Scope: multiple module groups touched."
		else
			audit_concise_problems << "Scope: unmatched paths — classify via scope.path_groups."
		end
	end
		write_and_print_pr_monitor_report(
			report: monitor_report.merge(
				default_branch_baseline: default_branch_baseline,
				audit_status: audit_state
			)
		)
	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
	audit_state == "block" ? EXIT_BLOCK : EXIT_OK
end

#check!Object

Thin focused command: show required-check status for the current branch’s open PR. Always exits 0 for pending or passing so callers never see a false “Error: Exit code 8”.



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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/carson/runtime/audit.rb', line 123

def check!
	unless head_exists?
		puts_line "Checks: no commits yet."
		return EXIT_OK
	end
	unless gh_available?
		puts_line "Checks: gh CLI not available."
		return EXIT_ERROR
	end

	pr_stdout, pr_stderr, pr_success, = gh_run(
		"pr", "view", current_branch,
		"--json", "number,title,url"
	)
	unless pr_success
		error_text = gh_error_text( stdout_text: pr_stdout, stderr_text: pr_stderr, fallback: "no open PR for branch #{current_branch}" )
		puts_line "Checks: #{error_text}."
		return EXIT_ERROR
	end
	pr_data = JSON.parse( pr_stdout )
	pr_number = pr_data[ "number" ].to_s

	checks_stdout, checks_stderr, checks_success, checks_exit = gh_run(
		"pr", "checks", pr_number, "--required", "--json", "name,state,bucket,workflow,link"
	)
	if checks_stdout.to_s.strip.empty?
		error_text = gh_error_text( stdout_text: checks_stdout, stderr_text: checks_stderr, fallback: "required checks unavailable" )
		puts_line "Checks: #{error_text}."
		return EXIT_ERROR
	end

	checks_data = JSON.parse( checks_stdout )
	pending = checks_data.select { |e| e[ "bucket" ].to_s == "pending" }
	failing = checks_data.select { |e| check_entry_failing?( entry: e ) }
	total = checks_data.count
	# gh exits 8 when required checks are still pending (not a failure).
	is_pending = !checks_success && checks_exit == 8

	if failing.any?
		puts_line "Checks: FAIL (#{failing.count} of #{total} failing)."
		normalise_check_entries( entries: failing ).each { |e| puts_line "  #{e.fetch( :workflow )} / #{e.fetch( :name )} #{e.fetch( :link )}".strip }
		return EXIT_BLOCK
	end

	if is_pending || pending.any?
		puts_line "Checks: pending (#{total - pending.count} of #{total} complete)."
		return EXIT_OK
	end

	puts_line "Checks: all passing (#{total} required)."
	EXIT_OK
rescue JSON::ParserError => e
	puts_line "Checks: invalid gh response (#{e.message})."
	EXIT_ERROR
end