Skip to content

fix(compliance): treat an undecided licence as CLA, not DCO - #80

Merged
aabusair merged 1 commit into
mainfrom
fix/requires-cla-fail-closed-on-undecided
Oct 5, 2026
Merged

aabusair merged 1 commit into
mainfrom
fix/requires-cla-fail-closed-on-undecided

Conversation

@aabusair

@aabusair aabusair commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

The defect

requires_CLA() is the only question the gate asks. It returned:

return bool(res.get("requires_CLA", True))

get_license_decision reports requires_CLA: None when it identified a licence but couldn't decide whether it's permissive. bool(None) is False, so an undecided licence came out as DCO, the weaker document.

The paths that fail to identify a licence already return True (no LICENSE file, no confident text match). This was the one path that failed open.

The fix

return res.get("requires_CLA") is not False

Only an explicit False means DCO now. The fix is in the facade, the enforcement boundary, and not in get_license_decision. The org licence reports read None there as "unknown", which is the honest answer for a report.

Impact today: none

  • is_permissive_with_reason returns None only for an SPDX expression that parses to no clauses ("WITH", "()"). _is_unit_permissive returns True/False for any non-empty licence.
  • The 2026-10-04 licence report put 0 of 106 public repos on this path, so no repo's CLA/DCO decision changes on merge.
  • It matches the original design intent recorded in .github/scripts/old/check_and_install_stub.py: "if requires_CLA() returns None or raises -> treat as require CLA."

Tests: 9 new, 137 total, all passing

Every existing test replaced requires_CLA wholesale, so the facade had no coverage at all. The new tests stub one layer lower so the real function runs:

Test Covers
None / False / True / missing key The facade's full truth table
Detector returns None for "WITH" and "()" Shows the path exists in shipped code, not just in a hand-made dict
End-to-end via get_license_decision Only the GitHub API call is faked. Also pins that the decision record still says None for the reports
End-to-end with MIT Positive control: a permissive licence stays DCO
Gate via process_single_pr, decision None Status is CLA Missing
Gate via process_single_pr, decision False Positive control: DCO Missing

Mutation-checked. Each wrong implementation fails a different set of tests:

  • the original bool(...): 3 failures
  • always-CLA over-correction: 3 failures
  • is True mis-fix: 4 failures

Hermetic. All 9 pass with every sync and async socket connection blocked (0 network attempts).

Rollout note

Both production workflows load scripts/ from main unpinned, so this is live org-wide on the next sweep after merge. Given the zero live hits above, nothing observable changes.

🤖 Generated with Claude Code

requires_CLA() is the only question the gate asks, and it returned
bool(res.get("requires_CLA", True)). get_license_decision reports None
when it identifies a licence but cannot decide whether it is permissive,
and bool(None) is False - so an undecided licence was downgraded to DCO,
the weaker document. The paths that fail to identify a licence at all
(no LICENSE file, no confident match) already return True; this path
was the one exception.

Only an explicit False now means DCO. The fix sits in the facade, the
enforcement boundary, rather than in get_license_decision: the org
licence reports read None there as "unknown", which is the honest
answer for a report.

Reachability: is_permissive_with_reason only returns None for an SPDX
expression that parses to no clauses ("WITH", "()"), so this is latent.
The 2026-10-04 licence report found zero of 106 public repos on this
path, so no repo's decision changes today. It matches the original
design intent recorded in .github/scripts/old/check_and_install_stub.py:
"if requires_CLA() returns None or raises -> treat as require CLA".

Tests: every existing test replaced requires_CLA wholesale, so the
facade had no coverage. Nine new tests stub one layer lower so the real
facade runs, including an end-to-end path through get_license_decision
with only the GitHub call faked, and a gate-level path through
process_single_pr. Positive controls pin that permissive licences stay
DCO. Mutation-checked: the original bool(), an always-CLA
over-correction, and an `is True` mis-fix each fail 3-4 tests. All
nine pass with every socket connection blocked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@aabusair
aabusair merged commit bbd4fdc into main Oct 5, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant