Skip to content

Require the CLA gate status check on world-at-ruin #107

Closed

Description

@devantler

🤖 Generated by the Daily AI Engineer

Evidence: world-at-ruin#35 delivered a first-party CLA gate (CLA gate check from .github/workflows/cla.yaml) that blocks external PRs until their author signs CLA.md (world-at-ruin#2). The check currently fails red on unsigned external PRs but is not required by the repo's ruleset, so a merge is technically possible while it is red.
Audience/problem: the maintainer and agents relying on the CLA legal gate being unbypassable; today it depends on reviewer discipline (agents never merge external PRs per the trust gate, but the ruleset should encode it).
Proposed: add CLA gate to world-at-ruin's required status checks in the declarative org config (note: required_status_checks rulesets have historically been UI-created + Observe-imported — follow that established path).
Acceptance: an external (non-allowlisted) PR with a red CLA gate check cannot be merged; trusted-author PRs are unaffected (the check passes instantly via the allowlist).
Size: small.

Activity

  1. coderabbitai commented on Jul 17, 2026

    @coderabbitai
    🔗 Related PRs

    #74 - feat(rulesets): require the CodeRabbit review status check org-wide [merged]
    #75 - revert(rulesets): drop the provider-uncreatable CodeRabbit required-check ruleset [merged]
    #92 - docs: complete agent and deploy contributor guidance [merged]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. moved this to 📥 Backlog in 🌊 Project Boardon Jul 18, 2026
  3. devantler commented on Jul 28, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Verified ready to apply — and safe today (measured 2026-07-28)

    This issue proposed the change but left the risky part unstated: a required status check that does not
    report on every PR wedges the repo permanently. I verified each precondition against live state rather
    than reasoning about it.

    1. The exact context string is CLA gate. Not the workflow name (CLA) — the job name. Confirmed
    on two live PRs (world-at-ruin#468, #460): name=CLA gate, app github-actions, conclusion=success.

    ⚠️ The same workflow's other job, Record CLA signature, reports skipped on every pull_request
    run
    (it is issue_comment-gated). Requiring that context would block every merge. Require only
    CLA gate.

    2. It reports on every PR. cla.yaml is on: pull_request with no path filter and no branch
    filter
    , and cla-check is gated on github.event_name == 'pull_request'. There is no configuration
    under which a PR silently lacks the check.

    3. No PR author is currently blocked. Every author who has ever opened a PR on world-at-ruin —
    devantler (185) and dependabot[bot] (15) across the last 200 PRs — is already in the workflow's
    ALLOWLIST, so the gate passes instantly for them. Making it required blocks nothing that exists today.

    4. Residual risk, and it is a maintainer decision — not mine to close. The allowlist is
    devantler dependabot[bot] renovate[bot] github-actions[bot] coderabbitai[bot]. It does not include
    cursor[bot], which is a trusted PR author under the current contract. That lane has never opened a PR
    on this repo, so nothing is blocked now — but if it ever does, a required CLA gate would hold it until
    the allowlist is widened. I am deliberately not widening it: this is a legal gate (copyright
    assignment), so who bypasses it is your call, not an agent's.

    Applying it

    Rulesets here are not declaratively managed (.github/deploy/ covers labels, teams and team-repository
    grants — there is no deploy/rulesets/), so this is a repository-settings change and I am leaving it to
    you rather than mutating settings from an unattended run.

    The target is ruleset 14974342 ("Require status checks to pass"), which today requires only
    CI - Required Checks. This reads the current ruleset and adds one context, preserving everything else —
    it is derived rather than hand-written, so it cannot drop existing state, and unique_by makes it
    idempotent (verified by applying the transform twice — identical output):

    gh api repos/devantler-tech/world-at-ruin/rulesets/14974342 \
        --jq '{name,target,enforcement,conditions,rules}' \
      | jq '(.rules[] | select(.type=="required_status_checks") | .parameters.required_status_checks)
            |= (. + [{"context":"CLA gate"}] | unique_by(.context))' \
      | gh api --method PUT repos/devantler-tech/world-at-ruin/rulesets/14974342 --input -

    Dry-run output of the transform:
    [{"context":"CI - Required Checks"},{"context":"CLA gate"}]

    Reverting is the same command with unique_by(.context) replaced by
    map(select(.context != "CLA gate")).

    Acceptance check after applying: open any PR and confirm CLA gate appears under required checks and
    that a trusted-author PR still merges (it should pass in seconds via the allowlist).

  4. devantler commented on Jul 28, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Investigated — the proposed approach needs one correction, and the enforcing step is yours, not an agent's.

    Correction: this must be repo-scoped, not the org ruleset. The issue proposes adding CLA gate to "the declarative org config". Measured today:

    • world-at-ruin has no repo-level status-check ruleset — it inherits org ruleset 14974342 ("Require status checks to pass", source_type: Organization), whose required contexts are currently exactly ["CI - Required Checks"].
    • .github/workflows/cla.yaml exists on world-at-ruin only (checked ksail, platform, monorepo, actions — all absent).

    A required status check that never reports leaves a PR permanently un-mergeable, so adding CLA gate to the org ruleset would wedge every other repo in the org. The right shape is a repo-scoped RepositoryRuleset for world-at-ruin, matching the existing deploy/repository-rulesets/require-merge-queue-on-platform.yaml pattern.

    Why no PR is attached. Every ruleset resource in deploy/ is managementPolicies: ["Observe"], and organization-rulesets/README.md explicitly warns against promoting past Observe without backfilling from status.atProvider. So the declarative layer observes rulesets; it cannot create one. The enforcing change is a repository-settings mutation, which is the maintainer's to make — I am not going to change a security setting out from under you, and a PR adding an inert Observe file for a ruleset that does not exist yet would misrepresent the state as delivered.

    Exact change to apply (world-at-ruin → Settings → Rules → New ruleset):

    • Target: Branch, default branch
    • Rule: Require status checks to pass
    • Required check: CLA gate
    • Enforcement: Active

    Once it exists, the follow-up is mechanical and I can take it: add deploy/repository-rulesets/require-cla-gate-on-world-at-ruin.yaml in Observe mode with crossplane.io/external-name set to the bare numeric ruleset id (per that directory's README — the Terraform <repo>:<id> form fails the provider's strconv.ParseInt), and add the row to its table.

    On the acceptance criterion — "trusted-author PRs are unaffected (the check passes instantly via the allowlist)" is worth verifying against a real trusted PR after the ruleset goes active, rather than assumed: if the allowlist path skips the job rather than reporting success, a required check would block trusted PRs too. That is the failure mode to watch for on the first PR after enabling.

  5. self-assigned this
    on Jul 28, 2026
  6. devantler commented on Jul 28, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    The proposed approach would break every repo in the org — corrected design below

    Investigated this end to end. The acceptance criteria are sound, but the ruleset this issue points
    at is org-scoped, not repo-scoped
    , so implementing it as written would have caused a portfolio-wide
    outage.

    What the investigation found

    world-at-ruin's "Require status checks to pass" ruleset (14974342) reports
    source_type: Organization — it is inherited from the org and currently requires exactly one check,
    CI - Required Checks. Adding CLA gate to it would demand that check on every repository in
    devantler-tech
    , and only world-at-ruin has a CLA workflow. Every other repo's PRs would sit
    permanently on "Expected — waiting for status to be reported".

    The correct shape is a new repo-scoped RepositoryRuleset on world-at-ruin alone.

    The gate itself is safe to require — verified behaviourally, not assumed

    • It runs on every PR. cla-check is on: pull_request with no path or branch filter, and recent
      pull_request runs complete success.
    • All actual authors are allowlisted. The only PR authors on this repo across the last 100 PRs are
      devantler (86) and dependabot[bot] (14); both are in the workflow's ALLOWLIST and exit 0
      immediately.
    • The issue_comment trigger does not poison the gate. Those runs are skipped (the job's if
      is false), which I expected might overwrite a PR's green — it does not: they attach to the
      base-branch SHA, never to a PR head.
    • Coverage today: 6 of 8 open PRs already show CLA gate: success at their current head. The two
      without it are Reject incomplete OIDC reads before publisher identity admission #488 (conflicted, so no merge ref and no pull_request run — it cannot merge anyway)
      and Refuse occupied versions before default publisher writes #492 (fast-moving head). Both clear on their next push.

    Remaining risk worth stating

    The allowlist omits cursor[bot] and botantler-1, which are trusted PR authors elsewhere in the
    portfolio. Neither has ever opened a PR here, so this is theoretical today — and it fails safe
    (a blocked PR, never a bypassed gate). Deliberately not widening a legal gate's allowlist as a
    side effect of this change; that belongs in its own issue if either lane starts publishing here.

    Blocked on a permission I do not hold

    Creating the ruleset is a repository-settings mutation, and the agent runtime's permission layer
    denied it. I do not widen my own guards, so this needs the maintainer. The exact, ready-to-apply
    payload:

    gh api --method POST repos/devantler-tech/world-at-ruin/rulesets --input - <<'JSON'
    {
      "name": "Require the CLA gate",
      "target": "branch",
      "enforcement": "active",
      "conditions": { "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] } },
      "rules": [
        {
          "type": "required_status_checks",
          "parameters": {
            "strict_required_status_checks_policy": false,
            "required_status_checks": [ { "context": "CLA gate" } ]
          }
        }
      ]
    }
    JSON

    No bypass actors, by design — the point of the issue is that the legal gate is unbypassable.

    Once it exists, the declarative record follows the established Observe-first convention: a new
    deploy/repository-rulesets/require-cla-gate-on-world-at-ruin.yaml whose
    crossplane.io/external-name is the bare numeric ruleset id returned by that call, plus its
    kustomization.yaml entry and README table row. That step is a normal PR and I can ship it as soon
    as the id exists.

    Unassigning myself so this is not read as a live claim while it waits on the maintainer.

  7. devantler commented on Aug 2, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Verified and prepared — blocked only by a runtime permission, not by anything about the change

    The premise is confirmed live. world-at-ruin ruleset 14974342 ("Require status checks to pass", active, scoped to ~DEFAULT_BRANCH) currently requires exactly one context:

    CI - Required Checks
    

    CLA gate is not among them, so the gate can indeed be red while a merge is technically permitted — exactly as this issue states.

    The over-tightening check, measured before proposing the change

    Requiring a check that does not always run would block every PR in the repo, so I measured CLA gate across the 12 most recent world-at-ruin PRs (#681–#695) by reading each head's check-runs:

    result count
    CLA gate present and success 12 / 12
    CLA gate absent 0

    The companion Record CLA signature check is skipped on all 12, which is the allowlist path behaving as designed. So requiring CLA gate cannot block trusted-author or bot PRs — it passes instantly for them — and bites only on an unsigned external contributor, which is the stated intent.

    The exact change, ready to apply

    PATCH /repos/devantler-tech/world-at-ruin/rulesets/14974342 with the existing parameters preserved and one context appended:

    {
      "rules": [{
        "type": "required_status_checks",
        "parameters": {
          "strict_required_status_checks_policy": false,
          "do_not_enforce_on_create": true,
          "required_status_checks": [
            { "context": "CI - Required Checks" },
            { "context": "CLA gate" }
          ]
        }
      }]
    }

    strict_required_status_checks_policy: false, do_not_enforce_on_create: true, the ~DEFAULT_BRANCH condition and the OrganizationAdmin bypass actor are all carried over unchanged — this appends one context and alters nothing else. It is fully reversible by removing that one entry.

    Why I did not apply it

    The agent runtime's permission layer denied the ruleset mutation. Widening my own guard to get around that is precisely what the engineering contract forbids — a permission expansion is an authorisation change and the maintainer's call, never something the agent grants itself. So this is recorded rather than forced.

    This needs one of: the maintainer applying the PATCH above (or the equivalent two clicks in the ruleset UI — the issue notes required-status-check rulesets have historically been UI-created), or a runtime grant for ruleset mutation on devantler-tech repos, after which a later tick can apply it directly.

    Everything else the acceptance criteria ask for is already established: the change is a pure tightening, trusted PRs are provably unaffected, and the exact payload is above.

  8. devantler commented on Aug 5, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Investigated — here is exactly what is needed, and the one step I cannot take unattended

    Verified.

    1. Check name is exactly CLA gate. world-at-ruin/.github/workflows/cla.yaml job cla-check sets name: CLA gate (the workflow itself is named CLA, so the workflow name is not the context to require — an easy thing to get wrong).
    2. Repo-scoped rulesets are managed in deploy/repository-rulesets/, one file per rule, named <verb>-on-<repo>.yaml, so the target file is require-cla-gate-on-world-at-ruin.yaml.
    3. required_status_checks is NOT in the provider's unsupported list in deploy/organization-rulesets/README.md, and the org already runs an imported "Require status checks to pass" ruleset — so the rule type is expressible in principle.
    4. A net-new managed ruleset has precedent — protect-release-tags.yaml is Observe + Create + Update + LateInitialize with no external-name, so Crossplane creates it on first reconcile. That is the shape a net-new repo ruleset would take.

    Blocker, named precisely. Every existing RepositoryRuleset in this repo is an Observe-only import whose external-name is the numeric id of a ruleset that already exists. So the Observe path cannot be used until the ruleset exists, and the Create path needs the provider's exact requiredStatusChecks schema (requiredCheck → context/integrationId, and the strict-policy field name). I could not authoritatively verify that schema this run:

    • no cluster access to read repositoryrulesets.repo.github.m.upbound.io — the CRD is the authority, and kubectl returned nothing;
    • the CRD is not vendored anywhere in platform/ or this repo;
    • the Terraform registry page is JS-rendered and returned no schema.

    I am not guessing a schema for a security control. A malformed ruleset either silently fails to reconcile — leaving the gate un-enforced while the config looks done, which is worse than the status quo — or applies partially.

    Two ways to finish, whichever you prefer:

    • UI + import (the established path this issue already names). Add CLA gate to a world-at-ruin branch ruleset in the UI; I then adopt it Observe-first exactly like require-merge-queue-on-platform.yaml, using gh api repos/devantler-tech/world-at-ruin/rulesets for the id. That is a small follow-up PR.
    • Managed Create. If a run gets cluster read access to that CRD, the schema can be read directly and shipped as a net-new managed file on the protect-release-tags.yaml pattern — no UI step at all.

    Not doing the API mutation myself unattended: creating a governance control that decides what can merge is a change I would rather you saw first. Everything else is ready.

  9. devantler commented on Aug 5, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Investigated — and the proposed direction needs correcting before anyone implements it

    Two live findings change what this issue should ask for.

    1. There is no repo-scoped ruleset on world-at-ruin to extend — every rule it has is
    organization-sourced.
    Read live today:

    GET repos/devantler-tech/world-at-ruin/rulesets/{16885082,6455804,4134020}
      → source=devantler-tech  source_type=Organization   (all of them)
    

    2. The org ruleset that carries required checks requires exactly one context, for every repo:

    GET repos/devantler-tech/world-at-ruin/rulesets/14974342
      name: "Require status checks to pass"   source_type: Organization
      required_status_checks: [ { context: "CI - Required Checks" } ]
      do_not_enforce_on_create: true   strict: false
    

    So the portfolio's established pattern is one aggregate required context per repo, not a list of
    individual checks — and CLA gate is a job in world-at-ruin's separate CLA workflow, not part of
    CI - Required Checks.

    ⚠️ This means "add CLA gate to the declarative org config" would be actively harmful as written.
    That org ruleset applies to every repository in the organisation. Every repo except world-at-ruin
    has no CLA workflow, so the context would never post and every PR org-wide would block forever
    waiting for a check that cannot arrive.

    What the fix actually is

    A new repository-scoped ruleset on world-at-ruin requiring the CLA gate context — the same
    shape as platform's repo-scoped merge-queue rule, which is the only precedent in
    deploy/repository-rulesets/.

    Why this is not agent-work — missing authority, verified rather than assumed

    Creating or editing a ruleset is a repository/organisation settings mutation, which is the
    maintainer's. Confirmed live rather than inferred:

    GET orgs/devantler-tech/rulesets
      → 403 "Resource not accessible by personal access token"
    

    And the declarative layer cannot substitute: every manifest in deploy/organization-rulesets/ and
    deploy/repository-rulesets/ is pinned managementPolicies: ["Observe"], with the README warning
    against promoting past Observe (lossy round-trip from status.atProvider). The declarative path
    records a ruleset; it cannot create one.

    Maintainer action needed (one-off, UI): on devantler-tech/world-at-ruin, add a repository
    ruleset targeting the default branch requiring the CLA gate status check. Once it exists, adopting
    it is ordinary agent work — a RepositoryRuleset manifest in deploy/repository-rulesets/
    (require-cla-on-world-at-ruin.yaml), Observe-first, crossplane.io/external-name set to the bare
    numeric ruleset id, added to that directory's kustomization.yaml.

    Note for whoever writes that manifest: use the bare numeric id, not <repo>:<id> — see the
    correction already recorded in require-merge-queue-on-platform.yaml.

    Unassigning myself: the blocking half is not mine to perform, and a bare assignment would otherwise
    read as work in progress.

  10. added a commit that references this issue on Aug 5, 2026
    e1bf5aa
  11. moved this from 📥 Backlog to 👀 In Review in 🌊 Project Boardon Aug 5, 2026
  12. moved this from 👀 In Review to ✅ Done in 🌊 Project Boardon Aug 5, 2026
  13. devantler commented on Aug 5, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Delivered — #135 merged 23:41:08Z. Left open deliberately for the live read-back.

    deploy/repository-rulesets/require-cla-gate-on-world-at-ruin.yaml is on main: a repo-scoped RepositoryRuleset requiring the CLA gate context on the default branch, managed (Observe + Create + Update + LateInitialize, never Delete) so Crossplane creates it.

    Correcting the direction this issue proposed: it suggested adding the context to the existing required-checks ruleset. That does not work — every ruleset on world-at-ruin is organization-sourced, and the org's "Require status checks to pass" carries one context for all repos, so CLA gate there would block every repo without a CLA workflow. Repo-scoped is the only fit.

    What is already proven: the context string is the cla-check job name (the workflow is named CLA — requiring that would block every PR on a check that never reports); the CR schema came from the live CRD; and requiring it over-tightens nothing — 14/14 recent world-at-ruin PRs carry exactly one CLA gate, all success.

    What is NOT yet proven, and why this stays open. At 23:4xZ the resource had not reconciled:

    kubectl --context admin@prod get repositoryruleset.repo.github.m.upbound.io -A
      → only github-config/platform-require-merge-queue    (mine absent)
    gh api 'repos/devantler-tech/world-at-ruin/rulesets?includes_parents=false' --jq length
      → 0
    

    Closing on the merge would repeat platform#2922 exactly — a config that validated green, was read by its consumer, and never reached the running system. To close this, re-run both commands above (expect the CR SYNCED=True READY=True with a numeric external-name, and the repo-scoped count to become 1), then confirm the negative control: a PR with a red CLA gate is genuinely unmergeable. ⚠️ Query with -A — these CRs live in the github-config namespace, so an unqualified get returns nothing and reads as a failed rollout.

  14. devantler commented on Aug 6, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Verified live — the gate is in force. Closure confirmed on evidence.

    The previous run left a carry-forward to verify this after reconciliation, and noted the issue was
    being "left open deliberately for the live read-back". It was in fact already closed at
    2026-08-05T23:41:09Z — one second after #135
    merged, and one minute before that comment was written. A closing keyword survived in the merged
    commit despite the intent to use Part of. Recording the missing evidence here so the closure rests
    on a verified outcome rather than an accident of timing.

    1. The Crossplane resource reconciled.

    $ kubectl get repositoryruleset -A
    NAMESPACE       NAME                             SYNCED  READY  EXTERNAL-NAME  AGE
    github-config   world-at-ruin-require-cla-gate   True    True   20481768       17m
    

    2. The ruleset exists on GitHub and is active (repos/devantler-tech/world-at-ruin/rulesets/20481768):
    name Require CLA gate, target: branch, enforcement: active, conditions include: [~DEFAULT_BRANCH],
    required check CLA gate.

    3. It is actually IN FORCE on the branch, which is the part that matters. A defined-and-synced
    ruleset is not the same fact as an enforced one, so this reads the effective rules GitHub applies
    to the default branch rather than the ruleset's own definition:

    $ gh api repos/devantler-tech/world-at-ruin/rules/branches/main
    [{"checks":["CI - Required Checks"],"src":14974342},
     {"checks":["CLA gate"],       "src":20481768}]
    

    CLA gate is present, attributed to ruleset 20481768 — the one this issue delivered. The
    pre-existing org-sourced CI - Required Checks is still there beside it, so this added a
    requirement without displacing the existing one.

    Acceptance criteria met. An external PR with a red CLA gate can no longer be merged; the check
    passes instantly for allowlisted authors, so trusted-author PRs are unaffected (14/14 recent PRs on
    the repo carry exactly one CLA gate, all success — measured before merging #135, which is what
    ruled out over-tightening).

  15. devantler commented on Aug 6, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Live read-back passed — closing on evidence, not on the merge

    The verification I deferred this issue for has now run. Crossplane reconciled the manifest 84 seconds after #135 merged.

    Cluster side (kubectl --context admin@prod get repositoryruleset.repo.github.m.upbound.io -A):

    github-config   world-at-ruin-require-cla-gate   SYNCED=True  READY=True  EXTERNAL-NAME=20481768
    

    created 2026-08-05T23:42:32Z. The external-name is a real GitHub-assigned ruleset id, which is what proves the provider actually created the object rather than merely accepting the CR.

    GitHub side (gh api repos/devantler-tech/world-at-ruin/rulesets/20481768):

    {"name": "Require CLA gate", "source_type": "Repository", "target": "branch",
     "enforcement": "active", "conditions": ["~DEFAULT_BRANCH"],
     "checks": ["CLA gate"], "strict": [false]}

    and world-at-ruin's repo-scoped ruleset count went 0 → 1 (rulesets?includes_parents=false).

    Against the acceptance criteria: CLA gate is now an active required status check on the default branch, so an external PR whose gate is red cannot be merged — the gate is enforced by the ruleset rather than by reviewer discipline. Trusted-author PRs are unaffected: the check passes instantly via the workflow allowlist (measured beforehand — 14/14 recent PRs carried exactly one CLA gate, all success), and strict is deliberately false so no PR gained a rebase round-trip.

    Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Fields

No fields configured for Security.

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions