Repository navigation
fix(compliance): treat an undecided licence as CLA, not DCO - #80
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
requires_CLA()is the only question the gate asks. It returned:get_license_decisionreportsrequires_CLA: Nonewhen it identified a licence but couldn't decide whether it's permissive.bool(None)isFalse, 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
Only an explicit
Falsemeans DCO now. The fix is in the facade, the enforcement boundary, and not inget_license_decision. The org licence reports readNonethere as "unknown", which is the honest answer for a report.Impact today: none
is_permissive_with_reasonreturnsNoneonly for an SPDX expression that parses to no clauses ("WITH","()")._is_unit_permissivereturns True/False for any non-empty licence..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_CLAwholesale, so the facade had no coverage at all. The new tests stub one layer lower so the real function runs:None/False/True/ missing keyNonefor"WITH"and"()"get_license_decisionNonefor the reportsprocess_single_pr, decisionNoneCLA Missingprocess_single_pr, decisionFalseDCO MissingMutation-checked. Each wrong implementation fails a different set of tests:
bool(...): 3 failuresis Truemis-fix: 4 failuresHermetic. All 9 pass with every sync and async socket connection blocked (0 network attempts).
Rollout note
Both production workflows load
scripts/frommainunpinned, 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