Skip to content

Commit f2ca4a6

Browse files
Claude (agent)claude
andcommitted
fix(certify): reweight when code is unscorable instead of auto-failing
Previously, repos with no Python LOC got code_score=0, causing automatic FAILED certification even for well-governed Go/Rust/TS repos. Now reweights to governance 60% + deps 40% when code scoring is unavailable, with a transparent note in reasons. Before: prometheus/prometheus → FAILED (47.0) After: prometheus/prometheus → CERTIFIED (94.0) Found by scanning 10 Go repos: all failed despite excellent governance because code scoring was Python-only. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 581d68e commit f2ca4a6

2 files changed

Lines changed: 33 additions & 9 deletions

File tree

src/arbiter/certify.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,35 +68,51 @@ def certify(
6868
"""Run full certification assessment.
6969
7070
Weights: code quality 50%, governance 30%, dependencies 20%.
71+
When code is unscorable (no Python LOC, analyzers not installed),
72+
reweight to governance 60% + dependencies 40% instead of penalizing.
7173
"""
72-
code = code_score.overall if code_score.is_scorable else 0
74+
code = code_score.overall if code_score.is_scorable else None
7375
gov = governance_report.score
7476
deps = dep_report.score
7577

76-
overall = code * 0.50 + gov * 0.30 + deps * 0.20
78+
if code is not None:
79+
overall = code * 0.50 + gov * 0.30 + deps * 0.20
80+
else:
81+
# Reweight: skip code dimension entirely
82+
overall = gov * 0.60 + deps * 0.40
83+
code = 0 # for display purposes
84+
7785
overall = round(overall, 1)
7886

7987
reasons = []
88+
code_scorable = code_score.is_scorable
8089

8190
# Determine decision
8291
cert = CERT_THRESHOLDS["certified"]
8392
prov = CERT_THRESHOLDS["provisional"]
8493

85-
if (overall >= cert["overall"] and code >= cert["code"]
94+
# Skip code threshold checks when code is unscorable
95+
code_meets_cert = code >= cert["code"] if code_scorable else True
96+
code_meets_prov = code >= prov["code"] if code_scorable else True
97+
98+
if not code_scorable:
99+
reasons.append("Code quality not scored (no supported analyzers for primary language)")
100+
101+
if (overall >= cert["overall"] and code_meets_cert
86102
and gov >= cert["governance"] and deps >= cert["deps"]):
87103
decision = "CERTIFIED"
88-
elif (overall >= prov["overall"] and code >= prov["code"]
104+
elif (overall >= prov["overall"] and code_meets_prov
89105
and gov >= prov["governance"] and deps >= prov["deps"]):
90106
decision = "PROVISIONAL"
91-
if code < cert["code"]:
107+
if code_scorable and code < cert["code"]:
92108
reasons.append(f"Code score {code:.1f} below certified threshold ({cert['code']})")
93109
if gov < cert["governance"]:
94110
reasons.append(f"Governance score {gov:.1f} below certified threshold ({cert['governance']})")
95111
if deps < cert["deps"]:
96112
reasons.append(f"Dependency score {deps:.1f} below certified threshold ({cert['deps']})")
97113
else:
98114
decision = "FAILED"
99-
if code < prov["code"]:
115+
if code_scorable and code < prov["code"]:
100116
reasons.append(f"Code score {code:.1f} below minimum threshold ({prov['code']})")
101117
if gov < prov["governance"]:
102118
reasons.append(f"Governance score {gov:.1f} below minimum threshold ({prov['governance']})")

tests/test_certify.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,17 @@ def test_governance_checks_counted(self):
6161
assert result.governance_checks_passed == 7
6262
assert result.governance_checks_total == 10
6363

64-
def test_non_scorable_code(self):
64+
def test_non_scorable_code_reweights(self):
65+
"""Non-scorable code should reweight to governance 60% + deps 40%, not fail."""
6566
unscorable = RepoScore(overall=0, lint_score=0, security_score=0,
6667
complexity_score=0, total_findings=0, is_scorable=False)
6768
result = certify(unscorable, _FakeGovReport(80), _FakeDepReport(85), [])
68-
assert result.code_score == 0
69-
assert result.decision == "FAILED"
69+
# Reweighted: 80*0.6 + 85*0.4 = 48+34 = 82
70+
assert result.overall >= 80
71+
assert result.decision == "CERTIFIED" # governance + deps are strong enough
72+
73+
def test_non_scorable_notes_reason(self):
74+
unscorable = RepoScore(overall=0, lint_score=0, security_score=0,
75+
complexity_score=0, total_findings=0, is_scorable=False)
76+
result = certify(unscorable, _FakeGovReport(80), _FakeDepReport(85), [])
77+
assert any("not scored" in r for r in result.reasons)

0 commit comments

Comments
 (0)