Skip to content

feat(gate): add a CLA / license-compatibility gate dimension#2679

Merged
JSONbored merged 3 commits into
mainfrom
feat/cla-license-compatibility-gate
Jul 3, 2026
Merged

feat(gate): add a CLA / license-compatibility gate dimension#2679
JSONbored merged 3 commits into
mainfrom
feat/cla-license-compatibility-gate

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • Adds gate.claMode: off | advisory | block (.gittensory.yml, default off) — the gittensory analog of a "CLA assistant" check bot, since a repo that legally requires contributor sign-off before merge had no deterministic way to enforce it through the auto-merging gate.
  • Detection mirrors the existing review.pre_merge_checks titleContains/descriptionContains deterministic-phrase-match pattern exactly (src/review/pre-merge-checks.ts): a new src/review/cla-check.ts evaluator checks for a maintainer-configured consent phrase in the PR body (case-insensitive substring), and/or a named CLA-bot check-run's conclusion (success/neutral). Consent is satisfied when either configured method holds.
  • A cla_consent_missing finding is always generated at severity warning; isConfiguredGateBlocker (src/rules/advisory.ts) is what escalates it to a hard blocker under claMode: block, exactly mirroring how manifest_missing_tests/manifest_linked_issue_required work — no new blocking mechanism, reuse of the existing one.
  • An unresolved check-run conclusion (a transient GitHub fetch failure, distinct from a resolved "no such check-run") produces cla_check_unresolved instead, which isEvaluationBlocker treats as a neutral HOLD that re-evaluates automatically — mirroring pre_merge_check_unresolved — so a flaky fetch can never manufacture a false close.
  • New src/github/backfill.ts export fetchNamedCheckRunConclusion (best-effort, single-call, fails to undefined not a false negative) resolves the check-run-conclusion detection mode, following the same pattern as the existing fetchRequiredStatusContexts.
  • Wired through every site the gate-setting-wiring template (reference.md) requires: types.ts RepositorySettings, openapi/schemas.ts RepositorySettingsSchema (regenerated apps/gittensory-ui/public/openapi.json), focus-manifest.ts (gate.claMode + gate.cla.{consentPhrase,checkRunName} parse/round-trip/resolveEffectiveSettings), advisory.ts GateCheckPolicy, queue/processors.ts (gateCheckPolicy + the live evaluation call site), and predicted-gate.ts (pre-submission prediction parity — the metadata-only predictor never resolves a live check-run, so it only predicts the phrase-match method and discloses the check-run-only gap by never producing a false positive there).
  • Documented in .gittensory.yml.example under the gate: block, matching the style of gate.size/gate.premergeContentRecheck.
  • The owner/admin exemption is inherited, not reimplemented: a block-mode CLA finding reaches the disposition planner exactly like any other configured blocker (conclusion: "failure" + the finding's title), so it automatically gets the same standing isContributor guard every other blocker gets (src/settings/agent-actions.ts) — one-shot close for a contributor PR, held for the owner/admin. Verified with a dedicated test.
  • Off by default: claMode unset ⇒ undefined (no DB column, mirrors sizeGateMode/gate.premergeContentRecheck — config-as-code only), so a repo that has not opted in makes no extra GitHub call and pushes no finding.

Closes #2564

Scope

  • The PR title follows type(scope): short summary Conventional Commit format, for example fix(api): restore profile access checks.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked an issue, or this is small enough that the summary explains why an issue is not needed.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally (unsharded) — the new cla-check.ts evaluator is 100% lines/100% branches; every branch on every changed line in backfill.ts, advisory.ts, predicted-gate.ts, focus-manifest.ts, and processors.ts is exercised on both sides (verified line-by-line against coverage/lcov.info).
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate — 0 vulnerabilities
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries — test/unit/cla-check.test.ts (14 tests: phrase-match, check-run-conclusion, either-method contract), test/unit/gate-check-policy.test.ts (block/advisory/off, unresolved-hold, resolveEffectiveSettings wiring), test/unit/focus-manifest.test.ts (parse/round-trip/public-safe filtering of the consent phrase and check-run name), test/unit/predicted-gate.test.ts (pre-submission prediction parity, including the check-run-only-config HOLD case), test/unit/backfill.test.ts (fetchNamedCheckRunConclusion fetch/fail-safe branches), test/unit/queue.test.ts (5 live end-to-end webhook tests: phrase block, phrase pass, off-by-default, check-run pass, check-run fail), test/unit/agent-actions.test.ts (owner-exemption for a CLA-flavored blocker).

Ran npm run test:ci end to end locally (all steps, including ui:build) — fully green, exit code 0. Also ran npm run test:coverage standalone to inspect coverage/lcov.info line-by-line for my changed ranges.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests. (N/A — no auth/CORS/session surface touched.)
  • API/OpenAPI/MCP behavior is updated and tested where needed — RepositorySettingsSchema extended with claGateMode/claConsentPhrase/claCheckRunName, openapi.json regenerated, ui:openapi:settings-parity passes.
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks. (N/A — no UI changes; this mirrors sizeGateMode, which also has no dashboard toggle, config-as-code only.)
  • Visible UI changes include a UI Evidence section — N/A, no visible/UI changes in this PR.
  • Public docs/changelogs are updated where needed (.gittensory.yml.example); CHANGELOG.md is untouched (not a release-prep PR).

Notes

  • Deliberately left the DB-column/dashboard-toggle path out of scope: claMode mirrors sizeGateMode's established "config-as-code only, no DB column" precedent (also used by gate.dryRun/gate.premergeContentRecheck/gate.aiReview.closeConfidence) rather than the full-parity DB path manifestPolicyGateMode uses — this keeps the PR narrow and matches an existing, proven pattern for a new opt-in gate dimension; a follow-up could add the dashboard toggle if wanted.
  • The metadata-only pre-submission predictor (predicted-gate.ts) cannot resolve a live check-run before a PR exists, so a check-run-only CLA config predicts a HOLD (never a false pass or false block) rather than a real verdict — this is called out inline and is the same limitation the predictor already has for path-gated pre_merge_checks.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 3, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
gittensory-ui 42aa98d Commit Preview URL

Branch Preview URL
Jul 03 2026, 06:39 AM

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.13%. Comparing base (96d2980) to head (42aa98d).
⚠️ Report is 7 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2679   +/-   ##
=======================================
  Coverage   96.13%   96.13%           
=======================================
  Files         240      241    +1     
  Lines       26985    27030   +45     
  Branches     9792     9821   +29     
=======================================
+ Hits        25941    25986   +45     
  Misses        433      433           
  Partials      611      611           
Files with missing lines Coverage Δ
src/github/backfill.ts 96.99% <100.00%> (+0.02%) ⬆️
src/openapi/schemas.ts 100.00% <ø> (ø)
src/queue/processors.ts 92.70% <100.00%> (+0.01%) ⬆️
src/review/cla-check.ts 100.00% <100.00%> (ø)
src/rules/advisory.ts 97.68% <100.00%> (+0.02%) ⬆️
src/rules/predicted-gate.ts 100.00% <100.00%> (ø)
src/signals/focus-manifest.ts 99.33% <100.00%> (+0.01%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JSONbored JSONbored self-assigned this Jul 3, 2026
@loopover-orb

loopover-orb Bot commented Jul 3, 2026

Copy link
Copy Markdown

Warning

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏸️ Gittensory review result - manual review recommended

Review updated: 2026-07-03 05:58:21 UTC

17 files · 1 AI reviewer · 2 blockers · readiness 98/100 · CI green · dirty

⏸️ Suggested Action - Manual Review

  • AI reviewers agree on a likely critical defect: src/review/cla-check.ts:61 treats an unresolved check-run as a hard `cla_consent_missing` whenever `consentPhrase` is also configured but absent, so a transient GitHub read failure can block a PR that would have satisfied the configured check-run method
  • hold whenever any configured method that could satisfy consent is unresolved, or explicitly require both methods instead of documenting either-method semantics. — Resolve the flagged defect, or override if the AI reviewers are mistaken, then re-run the gate.
  • Possible leaked secret in the diff (generic_secret_assignment) — Remove the secret from the diff, rotate the exposed credential, then re-run the gate.

Review summary
The PR wires a new config-driven CLA gate through manifest parsing, effective settings, live queue evaluation, policy escalation, prediction, schema, and tests. The phrase-only path is coherent, but the check-run path has reachable state-handling bugs: unresolved or pending external checks can produce the wrong gate outcome, including false hard failures and advisory-mode holds.

Blockers

  • src/review/cla-check.ts:61 treats an unresolved check-run as a hard `cla_consent_missing` whenever `consentPhrase` is also configured but absent, so a transient GitHub read failure can block a PR that would have satisfied the configured check-run method; hold whenever any configured method that could satisfy consent is unresolved, or explicitly require both methods instead of documenting either-method semantics.
  • src/github/backfill.ts:2384 converts a matching in-progress check-run with `conclusion: null` into an empty string, and src/review/cla-check.ts:61 then treats that as missing consent, so `claMode: block` can fail a PR before the named check has finished; return `undefined` or another hold sentinel for a present check-run whose conclusion is not final.
  • src/rules/advisory.ts:847 makes `cla_check_unresolved` an unconditional evaluation blocker, so `gate.claMode: advisory` with check-run-only detection can hold the gate even though advisory mode should surface findings without affecting the verdict; scope the hold to block mode or emit a normal warning under advisory mode.
Nits — 6 non-blocking
  • src/github/backfill.ts:2364 says a >100-check-run first-page miss degrades to `undefined`, but the implementation returns `null` when the named check is not on page 1, so the comment and behavior disagree.
  • src/rules/predicted-gate.ts:226 says check-run-only prediction produces no finding, but the code calls `evaluateClaCheck` with `checkRunConclusion: undefined` and now predicts a neutral hold.
  • Add unit coverage for `claMode: advisory` plus check-run-only unresolved data so the gate verdict stays advisory-only.
  • Add a live queue test where the named check-run is present with `status: in_progress` and `conclusion: null`, then assert the gate holds rather than fails.
  • Either paginate `fetchNamedCheckRunConclusion` until the named run is found or document and test that page-1 absence is treated as inconclusive rather than resolved missing.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

Concerns raised — review before merging

  • src/review/cla-check.ts:61 treats an unresolved check-run as a hard `cla_consent_missing` whenever `consentPhrase` is also configured but absent, so a transient GitHub read failure can block a PR that would have satisfied the configured check-run method; hold whenever any configured method that could satisfy consent is unresolved, or explicitly require both methods instead of documenting either-method semantics.
  • Possible leaked secret in the diff (generic_secret_assignment) — Remove the secret from the diff, rotate the exposed credential, then re-run the gate.
Signal Result Evidence
Code review ❌ 2 blockers 1 reviewer
Linked issue ✅ Linked #2564
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (size label size:L; 1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 61 registered-repo PR(s), 52 merged, 500 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 61 PR(s), 500 issue(s).
Gate result ❌ Blocking Repo-configured hard blocker found.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository registration is not available in the local Gittensory cache.
  • Public profile languages: not available
  • Official Gittensor activity: 61 PR(s), 500 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • No action.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.

  • Re-run Gittensory review

JSONbored added 3 commits July 2, 2026 23:36
Common OSS gate bots (the "CLA assistant" pattern) have no analog here, and
because this gate auto-merges green PRs with no human in the loop, a repo
that legally requires contributor sign-off before merge had no deterministic
way to enforce that through gittensory. This adds gate.claMode: off/advisory/
block, mirroring the existing pre_merge_checks titleContains/descriptionContains
deterministic-phrase-match pattern (src/review/pre-merge-checks.ts) plus a
second detection method, a named CLA-bot check-run's conclusion, satisfied by
EITHER method. Off by default, so a repo that has not opted in sees zero
behavior change.

Wired through the full config-as-code chain (types.ts RepositorySettings,
openapi/schemas.ts RepositorySettingsSchema, focus-manifest.ts gate.claMode +
gate.cla parsing/round-trip/resolveEffectiveSettings, advisory.ts
GateCheckPolicy + isConfiguredGateBlocker + isEvaluationBlocker,
queue/processors.ts gateCheckPolicy + the live evaluator call, and
predicted-gate.ts for pre-submission prediction parity), the same sites every
other GateRuleMode gate dimension touches. An unresolved check-run conclusion
(a transient fetch failure, not a resolved "no such check-run") HOLDS the
gate (cla_check_unresolved) instead of failing closed, mirroring
pre_merge_check_unresolved. The block-mode blocker integrates with the
existing gate disposition machinery unmodified, so it inherits the standing
owner/admin auto-close exemption for free rather than re-implementing it.

Closes #2564
Addresses gate-review findings on this PR:
- evaluateClaCheck now HOLDS (cla_check_unresolved) whenever a
  configured check-run's conclusion is unresolved, even when
  consentPhrase is also configured but not yet satisfied -- deciding
  purely from an unsatisfied phrase could hard-fail a PR the check-run
  method would have saved.
- fetchNamedCheckRunConclusion now returns undefined (unresolved) for
  a matching check-run that has not finished yet (status !== completed,
  conclusion: null is GitHub's own "not decided" contract), instead of
  coercing it to an empty string that read as a resolved, non-satisfying
  conclusion.
- cla_check_unresolved is no longer an unconditional evaluation
  blocker: it now only holds the gate under claMode: block, matching
  advisory mode's contract of surfacing findings without affecting the
  verdict.

Also renames a test fixture token to avoid tripping the generic-secret-
assignment scanner on a placeholder value.
@JSONbored
JSONbored force-pushed the feat/cla-license-compatibility-gate branch from 102829a to 42aa98d Compare July 3, 2026 06:38
@JSONbored
JSONbored merged commit dc90617 into main Jul 3, 2026
10 checks passed
@JSONbored
JSONbored deleted the feat/cla-license-compatibility-gate branch July 3, 2026 07:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier.

Development

Successfully merging this pull request may close these issues.

feat(gate): add a CLA / license-compatibility gate dimension

1 participant