Skip to content

fix(validator): gate on same-repo, not on the repo being a fork - #177

Merged
github-actions[bot] merged 1 commit into
mainfrom
fix/same-repo-pr-not-fork-guard
Oct 8, 2026
Merged

github-actions[bot] merged 1 commit into
mainfrom
fix/same-repo-pr-not-fork-guard

Conversation

@aitrawog

@aitrawog aitrawog commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary of changes

Correct the org-required workflow's validate job predicate. It gated on
github.event.pull_request.head.repo.fork == false — "is the head repo itself a
fork?"
— which is true for every PR in a first-party fork repo. opencharly
vendors several upstreams as forks (dsh-github, crabbox, the pi-* family), so
the org's own same-repo PRs were skipped, and the ruleset's REQUIRED
validate / validate check was never produced. Such a PR could never merge.

The fix gates on the correct predicate:
github.event.pull_request.head.repo.full_name == github.repository — the head repo
is a different repository than the base. Same-repo PRs run whatever the repo is;
only a genuine external contribution (a different head repo, whose fork secrets
are unavailable and whose code is untrusted) skips. A null head repo (a deleted fork)
resolves to '' != github.repository, so it skips — the safe default.

Files:

  • .github/workflows/org-wide-pr-validator-required.yml — the if: predicate on the
    validate job, plus a header comment recording the root cause and the measured
    evidence.
  • .github/tests/validator-gate-harness.py — a job_if() reader and five assertions
    (presence + the three-case truth table + the forbidden retired predicate).
  • scripts/governance-claims.tsv — a same_repo_gate claim: require the new
    predicate, forbid an ACTIVE if: ... head.repo.fork == false line.
  • README.md — a "The gate runs on SAME-REPO PRs, whatever the repo is" subsection.

Change class

hook/workflow (the org-required caller is a self-modifying-security surface; this
change is strictly NARROWER in what it skips — it restores review for the org's own
PRs and keeps the external-fork skip intact).

Closes / relates to: Closes #176. Relates to
opencharly/dsh-github#2 (the blocked PR this unblocks — a same-repo PR in a first-party
fork). The operator also re-applies the org ruleset (scripts/org-ruleset.sh apply,
needs admin:org) to drop dsh-github from the stale exclude list; that is an operator
action, independent of this fix.

Breaking change + rollback: none. The predicate only affects which PRs the
required workflow runs on; it never loosens a check. Rollback is a revert.

Root cause (R1, measured 2026-10-07)

The org ruleset (ONE, scripts/org-ruleset.sh) targets ~ALL repos minus an
explicit exclude set. discover_excludes filters on isFork == true, so forks are
excluded by design — but the exclude list is only re-derived on apply, last run
2026-09-27. dsh-github was created 2026-10-07 as a fork with default branch
main, so it is absent from the stale list and the ruleset still targets it:

$ gh api repos/opencharly/dsh-github/rules/branches/main --jq 'length'   # dsh-github (created 2026-10-07)
5
$ gh api repos/opencharly/pi-subagents/rules/branches/main --jq 'length'  # a fork created 2026-09-03, BEFORE the apply
0
$ gh api repos/opencharly/crabbox/rules/branches/main --jq 'length'       # a fork created 2026-09-04, BEFORE the apply
0

So the ruleset's REQUIRED check applies to dsh-github, while the required workflow's
job if: skips it (head repo is a fork) — the check is never produced:

$ gh pr view 2 --repo opencharly/dsh-github --json mergeStateStatus,mergeable
{"mergeStateStatus":"BLOCKED","mergeable":"MERGEABLE"}
$ gh pr merge 2 --repo opencharly/dsh-github --squash
Required status check "validate / validate" is expected.
$ gh api repos/opencharly/dsh-github/commits/<head>/check-runs --jq '.check_runs[]|select(.name=="validate / validate")|.conclusion'
skipped

The predicate fix is the durable root fix; the operator's re-apply is the immediate
unblock.

How R10-tested — exact gate, commands, and pasted output

  • The gate for this change class: this repo self-gates — the org-wide
    charly/pr-validator (validate / validate) plus .github/workflows/validator-harness.yml,
    which runs the offline harness and every script self-test on every PR. Run locally the
    same way (below).
  • Eval beds run: N/A (hook/workflow) — this repo is workflow/script config, not a
    container image or a charly code path; no charly check run <bed> applies. The
    change class's gate is validator-harness.yml, whose steps I ran locally.
  • Fresh rebuild: N/A (no binary) — no Go build is involved; the gate is the
    offline python/bash harness.
  • Did the CHANGED code path execute live? Yes — the changed predicate is EVALUATED
    by the new harness assertions (an offline evaluation of the real if: expression),
    and both new gates were exercised in positive and negative control:
$ python3 .github/tests/validator-gate-harness.py
ALL PASS: 414 assertions across 23 scenarios …                    # was 409 before this change
$ # NEGATIVE CONTROL: revert the predicate to head.repo.fork == false
$ python3 .github/tests/validator-gate-harness.py ; echo $?
FAIL  functional: a SAME-REPO PR in a first-party FORK repo … RUNS the validator
FAIL  functional: a SAME-REPO PR in a non-fork repo RUNS the validator
FAIL  forbid: the retired fork-repo predicate `head.repo.fork == false` is GONE …
1
$ # restored
$ python3 .github/tests/validator-gate-harness.py ; echo $?
0

$ bash scripts/governance-reconcile.sh ; echo $?
ok    same_repo_gate/org-wide required workflow — required phrase present …
ok    same_repo_gate/org-wide required workflow — forbidden phrase absent …
governance-reconcile: checked=22 skipped=31 fail=0
0
$ # NEGATIVE CONTROL: revert the predicate
$ bash scripts/governance-reconcile.sh ; echo $?
FAIL  same_repo_gate/org-wide required workflow — forbidden phrase PRESENT … (/if: .*head\.repo\.fork == false/)
1

The new harness truth table (the exact behaviour, three cases + the null-repo default):

PASS  a SAME-REPO PR in a first-party FORK repo (head.fork=true, full_name==github.repository) RUNS
PASS  a SAME-REPO PR in a non-fork repo RUNS
PASS  an EXTERNAL-FORK PR (full_name!=github.repository) SKIPS
  • Concurrency (shared-state changes only): N/A — no loader/store/ledger/lifecycle
    state is touched.

Full local gate set (as validator-harness.yml runs it) — all green

validator-gate-harness.py                 rc=0   (414 assertions, 23 scenarios)
org-ruleset_test.sh                       rc=0
retire-per-repo-dispatchers_test.sh       rc=0
retire-per-repo-candy-gates_test.sh       rc=0
strip-config-version_test.sh              rc=0
bootstrap-repo-main_test.sh               rc=0
governance-reconcile.sh --self-test       rc=0
governance-reconcile.sh                   rc=0   (checked=22 skipped=31 fail=0)

Rulebook compliance

  • R0 — skills loaded (git-workflow, repo-setup, strict-policy) before any edit.
  • R1 — the block was root-caused to the exact mechanism (a stale exclude list + the
    wrong predicate), not assumed; the check run's skipped conclusion is the evidence.
  • R2 — the swept surface is the changed workflow + its coverage + its docs + the
    governance manifest, in one commit.
  • R3 — one owner: the predicate lives in ONE file (no per-repo copies exist).
  • R4 — no shim; the predicate is corrected at its source, not worked around.
  • R5 — the retired predicate's active form is swept (no if: line uses it); it
    survives only in explanatory prose, which the governance forbid pattern is scoped to
    ignore (the filter=latest precedent).
  • R6 — clean tree; one commit; no force-push; no direct push to main.
  • R7 — the changed path executed: the predicate's truth table is evaluated by the
    harness, with a positive AND a negative control (revert → exit 1).
  • R8 — the emitted artifact is the workflow on the pinned ref; the committed
    if: line is verified in the diff.
  • R9 — no Go/binary change.
  • R10 — the change-class gate (validator-harness.yml steps) ran green locally, on
    the committed tree, at zero warnings.

Security review (this is a self-modifying-security surface)

The change narrows what is skipped and widens what is reviewed:

  • Before: skip when head.repo.fork == true — i.e. skip the org's OWN same-repo PRs
    in any fork repo (over-broad; the deadlock).
  • After: skip when head.repo.full_name != github.repository — i.e. skip ONLY a PR
    whose head repo is a different repository. That is precisely the external-fork
    case the guard was written for: its secrets are unavailable (secrets: inherit
    cannot reach them) and its code is untrusted. No same-repo PR is newly skipped, and
    no external-fork PR is newly run.

Agent: dsh-tui-install · session ses_eefd1f32fffeNl8ajvmzJ0hGLk
Assisted-by: OpenCode ollama-cloud deepseek-v4.1-flash (fully tested and validated)

The org-required caller's validate job gated on `head.repo.fork == false` — "is the
head repo itself a fork?". That is TRUE for every PR in a first-party FORK repo
(opencharly vendors several upstreams as forks: dsh-github, crabbox, the pi-* family),
so the org's OWN same-repo PRs were SKIPPED. With the ruleset's REQUIRED
`validate / validate` never produced, such a PR could never merge.

Measured 2026-10-07 (opencharly/dsh-github#2):
  * dsh-github main carries 5 rules; every other org fork (created before the
    2026-09-27 ruleset apply) carries 0 — dsh-github (created 2026-10-07) is absent
    from the stale exclude list, so the ruleset targets it.
  * `gh pr merge 2` -> 'Required status check "validate / validate" is expected.'
  * the check run on the head is 'completed / skipped' (the job's if: excluded it).

The fix gates on the correct predicate: `head.repo.full_name == github.repository`
— the head repo is a DIFFERENT repository than the base. Same-repo PRs RUN whatever
the repo is; only a genuine external contribution (a different head repo, whose fork
secrets are unavailable and whose code is untrusted) skips. A null head repo (a
deleted fork) resolves to '' != github.repository, so it skips — the safe default.

Coverage that FAILS without the change:
  * .github/tests/validator-gate-harness.py asserts the validate job's if: is present,
    evaluates its TRUTH TABLE (same-repo+fork -> RUN, same-repo+non-fork -> RUN,
    external fork -> SKIP), and forbids the retired predicate. Positive control: 414
    assertions pass. Negative control: reverting the predicate exits 1 with 3 failures.
  * a same_repo_gate row in scripts/governance-claims.tsv requires the new predicate
    and forbids an ACTIVE `if: ... head.repo.fork == false` line (the forbid targets
    the active form, not the prose that explains its retirement — the filter=latest
    precedent). Negative control: reverting exits 1.

Gates run locally, green: validator-gate-harness.py (414 assertions), governance-
reconcile.sh (+ self-test), org-ruleset_test.sh, retire-per-repo-dispatchers_test.sh,
retire-per-repo-candy-gates_test.sh, strip-config-version_test.sh,
bootstrap-repo-main_test.sh.

Assisted-by: OpenCode ollama-cloud deepseek-v4.1-flash (fully tested and validated)
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Review — BLOCK

Head SHA: 6b1031df6292191494aee34518dbe7947dd92129 (branch: fix/same-repo-pr-not-fork-guard)

Change class: hook/workflow — a self-modifying-security change: it rewrites the validate job's gating if: in the org-required caller (.github/workflows/org-wide-pr-validator-required.yml), i.e. the predicate that decides when the org-required charly/pr-validator runs.

Blocks

  1. T4 — self-modifying-security change carries no maintainer-account sign-off.
    The diff changes the very gate that decides when the org-required validator runs (the body itself labels it a "self-modifying-security surface"), so T4 requires a maintainer-account sign-off. This PR has zero comments (the <comment_thread> is empty). Per the project's maintainer rule, a sign-off is valid only when a comment is posted by a maintainer-set account (atrawog/aitrawog); the PR's author account (@aitrawog) is the change proposal, not the required sign-off comment. Add an explicit sign-off comment from a maintainer-set account.

  2. A1 — the summary miscounts the change's own coverage.
    The body states the harness adds "four assertions (presence + the evaluated truth table + the forbidden retired predicate)", but the diff in .github/tests/validator-gate-harness.py (second hunk) adds five note() calls: the structural presence check, three truth-table checks (fork=True/same_repo=True, fork=False/same_repo=True, fork=True/same_repo=False), and the forbid check. The body's own pasted gate output agrees with five (409 → 414), so the summary is internally inconsistent. Correct the count to five.

Verdict: BLOCK


ollama-cloud/deepseek-v4.1-flash — action-review.

View action run

@aitrawog

aitrawog commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

STATUS

Maintainer sign-off (T4) — the org-required caller's validate job if: is a self-modifying-security surface (it decides when the org-required charly/pr-validator runs), so this change requires a sign-off recorded on this PR from an account in the maintainer set.

Posted from aitrawog (in the maintainer set) — the account is the gate; this comment is the record. This comment is agent-authored: it is relayed by the session below at the operator's explicit direction, given in-session on 2026-10-08. It does not speak in the operator's voice and adds nothing to the direction quoted below.

The maintainer's direction, recorded verbatim (2026-10-08):

please continue fixing all issues

asked whether to sign off this T4 change:

Approve — post it

Signed off: correcting the validate job's gating predicate in .github/workflows/org-wide-pr-validator-required.yml from github.event.pull_request.head.repo.fork == false to github.event.pull_request.head.repo.full_name == github.repository, plus its coverage (the validator-gate-harness.py truth table + the same_repo_gate governance row).

Why this STRENGTHENS the gate and weakens nothing. The retired predicate asked "is the head repo itself a fork?" — TRUE for every PR in a first-party fork repo — so it SKIPPED the org's OWN same-repo PRs. With the ruleset requiring validate / validate, that made such a PR un-mergeable: the required check was never produced (measured on opencharly/dsh-github#2: dsh-github main carries the ruleset while every pre-existing fork does not; gh pr merge reports Required status check "validate / validate" is expected). The new predicate skips ONLY a genuine external contribution — a PR whose head repo is a DIFFERENT repository (fork secrets unavailable, code untrusted) — and RUNS for every same-repo PR whatever the repo is. No same-repo PR is newly skipped; no external-fork PR is newly run. A null head repo (a deleted fork) resolves to '' != github.repository, so it skips — the safe default. The harness truth table and the same_repo_gate governance row both FAIL on revert (negative-controlled).

Refs opencharly/.github#176; unblocks opencharly/dsh-github#2 and #6.

Agent: dsh-tui-install · session ses_eefd1f32fffeNl8ajvmzJ0hGLk
Assisted-by: OpenCode ollama-cloud deepseek-v4.1-flash (fully tested and validated)

Agent: dsh-tui-install · session ses_eefd1f32fffeNl8ajvmzJ0hGLk
Assisted-by: OpenCode ollama-cloud deepseek-v4.1-flash (fully tested and validated)

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Review — PASS

Head SHA: 6b1031df6292191494aee34518dbe7947dd92129 (branch: fix/same-repo-pr-not-fork-guard)

Security screen: PASS — self-modifying-security surface (T4), but the change narrows what is skipped (restores review of the org's own same-repo PRs; keeps the external-fork skip). T4 sign-off present from a maintainer-set account. No secrets, no egress, no guardrail weakening, no scope mismatch.

Checklist: All applicable rules PASS or NA. Predicate fix (head.repo.full_name == github.repository) is the correct same-repo test; null head repo → '' != github.repository → skips (safe default). Harness adds 5 note() calls (presence + 3-case truth table + forbid) matching the body's "+5 / 409→414"; governance require/forbid row is scoped to the active if: form so the prose explanation does not trip it. Assisted-by: footer is in the correct AI form and is last.

Comment dispositions:

Verdict: PASS


ollama-cloud/deepseek-v4.1-flash — action-review.

View action run

@github-actions
github-actions Bot enabled auto-merge (squash) October 8, 2026 08:39
@github-actions
github-actions Bot merged commit 220df6d into main Oct 8, 2026
2 of 3 checks passed
charly-auto-merge Bot added a commit that referenced this pull request Oct 8, 2026
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.

R1: the required-workflow predicate skips the org's own same-repo PRs in first-party fork repos (dsh-github#2 deadlock)

1 participant