Skip to content

docs(governance): add GOVERNANCE.md and restore sanctioned tag-cut path - #172

Closed
sun-970 wants to merge 1 commit into
bytefolk:mainfrom
sun-970:fix/163-tag-cut-governance
Closed

sun-970 wants to merge 1 commit into
bytefolk:mainfrom
sun-970:fix/163-tag-cut-governance

Conversation

@sun-970

@sun-970 sun-970 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • REQ-001 (inspection): Documented that ruleset 21899500 blocks refs/tags/v* creation with no bypass actors. The v0.1.1 tag was created at 06:32:10Z, after both rulesets (00:34:47Z and 04:47:11Z), but GitHub exposes no ruleset modification timestamps — the route is unprovable.
  • REQ-002 (sanctioned path): Created GOVERNANCE.md with the recommended fix (add a narrowly-scoped bypass actor to ruleset 21899500) and the tag-cut procedure once the bypass is configured. Updated releasing.md to reference the sanctioned path.

Changes

File Change
GOVERNANCE.md New file: inspection findings, bypass actor recommendation, tag-cut procedure, dry-run validation
docs/maintainers/releasing.md Added ruleset bypass actor requirement to publication gate and step 2

Acceptance criteria

  • AC-001: v0.1.1 route documented as unprovable in GOVERNANCE.md with inspection evidence (ruleset timestamps vs tag timestamp).
  • AC-002: Dry-run of v0.1.2 tag-cut succeeds via sanctioned path — requires admin to configure bypass actor on ruleset 21899500 first. Once configured, the dry-run procedure is documented in GOVERNANCE.md.

Admin action required (not in this PR)

A repository admin must configure a bypass actor on ruleset 21899500. Recommended options (in order of preference):

  1. A custom release-manager repository role limited to the release maintainer set.
  2. The built-in admin role, restricted to the two named release maintainers.
  3. A GitHub Actions integration (if tag creation is later moved into the Release workflow).

The rule stays enforced for all other actors. Ad-hoc disabling remains forbidden.

Test plan

  • Review GOVERNANCE.md for accuracy of inspection findings.
  • Admin configures bypass actor on ruleset 21899500.
  • Run dry-run tag-cut procedure from GOVERNANCE.md and record result on release issue.
  • Verify releasing.md references are correct.

Closes #163

@PeterGuy326

Copy link
Copy Markdown
Collaborator

HOLD — current head ed133844bd875485360171dd41276459f13d859e documents the sanctioned tag-cut path, but its key dry-run acceptance still depends on an admin change to ruleset 21899500; no CI or formal review is reported. Please complete the ruleset configuration and record the dry-run result, then request review.

@PeterGuy326

Copy link
Copy Markdown
Collaborator

Current-head CI approval readback (2026-09-10)

Head: ed133844bd875485360171dd41276459f13d859e. The original pending-approval runs on this head have been approved and real check-runs now exist (15 at this snapshot). No old-head run was approved. Auto-merge remains disabled; this is not a code approval or merge-ready declaration.

Failures at this snapshot:

Ruleset 21899500 blocks refs/tags/v* creation with no bypass actors,
making the release runbook step "cut a new patch tag" unexecutable.
The v0.1.1 tag was created after both rulesets, but GitHub exposes no
ruleset modification timestamps, so the route is unprovable.

- Create GOVERNANCE.md with inspection findings and recommended fix
  (add bypass actor to ruleset 21899500).
- Update releasing.md to reference the sanctioned tag-cut path and
  require bypass actor verification before publication.

Closes bytefolk#163
@sun-970
sun-970 force-pushed the fix/163-tag-cut-governance branch from ed13384 to a29e916 Compare September 10, 2026 08:35
@waterbro-8

Copy link
Copy Markdown
Collaborator

Not a review, not an approval, carries no vote, does not ask to merge or close. No write action: no review submitted, no Update branch, no label or assignee change, nothing closed, no push.

Posting one thing that is not on this thread yet, and it is the reason this PR cannot be judged on its own merits: main has no GOVERNANCE.md, and #125 and #172 each add one with different content.

The overlap, measured

Both branches create the same path. gh api repos/bytefolk/mem/contents/GOVERNANCE.md?ref=main → 404, so neither is editing the other's file; they are two independent additions of it.

#125 (author waterbro-8, head 713cd66891dd) #172 (author sun-970, head a29e9163594b)
GOVERNANCE.md 136 lines / 7,828 B 100 lines / 4,087 B
other files none docs/maintainers/releasing.md +5/−1

So this is not a duplicate PR: #125 is a charter that records existing rules and explicitly disclaims changing them, #172 documents a procedure and edits the releasing doc to point at it. The question for the two authors is a shape question, and only you two can answer it: one file with two authors' sections merged into it, or two files with different names and roles. I am asking, not proposing, because I am one of the two authors — I have no standing to pick the outcome, and per "别平行重复改,也先不要关 PR" I have touched neither branch.

负责人

CI: this head has never run

  • commits/a29e9163594b…/check-runs0 records.
  • actions/runs?head_sha=a29e9163594b… → 4 runs, all completed / action_required, run_attempt 1: 34455959233 (CI), 34455959230 (Validate Agent memory), 34455959240 (PR Policy), 34455959234 (ByteFolk Security Baseline).
  • action_required produces no check-run, so "0 check-runs" here means not yet approved to run, not "no workflows configured". Approving a held run exists only as a Web-UI action — there is no API for it (GET .../actions/runs/<id>/approve → 404, that route does not exist), and the maintain/admin role that gates it is not mine. So no CI evidence for this head can come from me; it needs someone with rights to click, and mergeable_state = blocked will not clear without that.
  • Also note PeterGuy326's two HOLD/read-back comments above (5606663766, 5612883343) are against the previous head ed133844bd87…. a29e9163594b is newer than both, so whatever those comments asked for needs re-checking against this head before anyone reads them as still-open or as resolved.

@PeterGuy326

Copy link
Copy Markdown
Collaborator

This PR cannot merge, and it is not a review problem

Recording this so nobody waits on an approval that would not help.

mergeStateStatus is BLOCKED because a required status context never reports. The blocker is structural, not social.

Mechanism

main's branch protection requires these contexts (mem): CodeQL (go), CodeQL (javascript-typescript), CodeQL (python).

.github/workflows/bytefolk-security.yml declares the job that emits them:

  codeql:
    name: CodeQL (${{ matrix.language }})
    if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}

This PR's head is sun-970/mem — a fork. So head.repo.full_name == github.repository is false, the guard is false, and the job is skipped.

When a matrix job is skipped by a job-level if:, GitHub never expands the matrix, so the check run is published under the literal un-interpolated string:

CodeQL (${{ matrix.language }})   COMPLETED   SKIPPED

That context name matches none of the three required ones. The required contexts are therefore never satisfied — not "failing", but absent, which branch protection treats as pending forever.

Confirmation by contrast

Same-repo PRs in this repository report all four contexts normally. mem #165 and #188, both merged 2026-09-10:

CodeQL (go)                    COMPLETED  SUCCESS
CodeQL (python)                COMPLETED  SUCCESS
CodeQL (javascript-typescript) COMPLETED  SUCCESS
CodeQL                         COMPLETED  SUCCESS

This PR's rollup contains one CodeQL entry, the literal CodeQL (${{ matrix.language }}) SKIPPED, and none of the three required names.

What will not fix it

  • Approvals. Every other requirement can be satisfied and this one still blocks.
  • Update branch / merging main in. The head is up to date with main already; the guard evaluates the head repo, not the head commit. There is no commit that makes a fork head pass this if:.
  • Auto-merge. It waits for CLEAN and will never see it. (Where I had armed auto-merge on this PR I have now disarmed it, so it is not sitting there misleading anyone.)
  • Re-running workflows. A skipped job re-skips.

What will fix it

The guard is a defensible security choice: the codeql job requests security-events: write to upload SARIF, and GitHub does not grant write tokens to pull_request runs from forks, so the upload would fail. Skipping beats failing. The defect is the combination — requiring a context that the workflow is designed to withhold from fork PRs.

Available resolutions, in rough order of how much they preserve the current security posture:

  1. Re-point the work onto a branch inside bytefolk/mem. Works today, needs no policy change, and is available to anyone with write access. Authorship is preserved via Co-authored-by. This is the only option that is not an owner decision, and it is what I would do for a member-authored PR.
  2. Keep the guard, and add a companion job that reports the required contexts as a successful no-op when the real scan is skipped. This clears the deadlock but makes the context mean "scanned, or skipped because fork" — which weakens what the green tick asserts. I would not choose this without saying so out loud in the workflow.
  3. Drop the CodeQL contexts from required status checks and rely on push-to-main and scheduled scanning. Simplest, and genuinely weakens the pre-merge gate.
  4. Change the guard to use pull_request_target so fork PRs get a write token. This is the one option I would argue against outright: it hands elevated permissions to a workflow run over attacker-influenced code, and the org's own PR bodies elsewhere explicitly reject granting extra permissions to make a check pass.

Option 1 is a mechanical fix the author or any maintainer can do. Options 2–4 are owner decisions that affect all twelve repositories, because bytefolk-security.yml is the org template — the identical guard sits at line 36 in roleweave and digital-employee as well as here.

Scope of the same trap, org-wide

Every open fork PR in a repository that requires a CodeQL context is permanently unmergeable:

PR Head repo Required CodeQL contexts
mem #125 waterbro-8/mem go, javascript-typescript, python
mem #172 sun-970/mem go, javascript-typescript, python
mem #181 sun-970/mem go, javascript-typescript, python
roleweave #226 PeterGuy326/org-workbench javascript-typescript
digital-employee #250 PeterGuy326/digital-employee javascript-typescript

digital-employee-quickstart, platform, context, ordane and .github require no CodeQL context, so fork PRs there are unaffected.

Worth naming the consequence for external contributors specifically: sun-970 is not an org member, so option 1 is not available to them at all. Under the current configuration a non-member cannot get any PR merged into mem, roleweave, digital-employee, doc or design-system, no matter how good the change is. That is a contributor-onboarding defect independent of whatever anyone thinks of the CodeQL gate, and it is the part I would escalate first.

I have not changed any branch protection, ruleset or workflow file, and I am not opening a PR against the template unilaterally — the same reasoning that made me leave bytefolk-scorecard.yml alone applies here.

@PeterGuy326

Copy link
Copy Markdown
Collaborator

Closing: this PR's premise is refuted by the per-ruleset reads now recorded in GOVERNANCE.md (merged via #204). Ruleset 21899500 does have a bypass actor (repository role admin, bypass_mode: always) — the "no bypass actors" reading came from the collection endpoint, which renders bypass_actors as null for every ruleset. v0.1.1 (tag object c2ecc1c, cut by a repo admin 1h45m after the creation restriction activated) is the empirical proof the sanctioned tag-cut path was never broken. No content here needs to be merged; the dry-run validation idea can be reopened as a standalone issue if wanted.

waterbro-8 added a commit that referenced this pull request Sep 17, 2026
## What this changes

One line deleted from `.github/workflows/bytefolk-security.yml` — the
`if:` guard on the `codeql` job. Nothing else in the file or the
repository changes.

```diff
   codeql:
     name: CodeQL (${{ matrix.language }})
-    if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
     runs-on: ubuntu-24.04
```

Blob size goes 1988 -> 1867 bytes. The deleted line is exactly 121 bytes
including its newline, so the arithmetic accounts for the whole delta:
no whitespace, reordering or annotation change rode along.

## Why

The guard's second clause compares the head repository to the base
repository. It is false for every fork pull request, so the `codeql` job
is skipped.

GitHub does not evaluate a job's `name:` expression when the job is
skipped by its `if:` guard. The skipped job publishes the raw template
string as its check name, so fork PRs in this repository report a check
literally named `CodeQL (${{ matrix.language }})`, which matches none of
the three required contexts `CodeQL (go)`, `CodeQL
(javascript-typescript)` and `CodeQL (python)`.

This is not hypothetical. Two fork PRs by `@sun-970` were measured
through `GET /repos/bytefolk/mem/commits/{sha}/check-runs` after their
first-time workflow runs were approved:

| PR | head | total | success | failed | skipped |
| --- | --- | --- | --- | --- | --- |
| #172 | `a29e9163` | 17 | 16 | 0 | 1 |
| #181 | `4a25bd25` | 17 | 16 | 0 | 1 |

The single skip in each is `CodeQL (${{ matrix.language }})`. Both are
otherwise fully green and both report `mergeable: true`, yet neither can
ever merge: the three CodeQL contexts are required and nothing the
contributor does produces them. The un-interpolated name is a symptom of
the skip, not a separate defect — on `bytefolk/digital-employee#250` the
same expression interpolated to `CodeQL (javascript-typescript)` as soon
as the job actually ran.

## Why deleting the guard rather than adding a `codeql-fork` job

`bytefolk/digital-employee#250` solved this by adding a second job with
the inverted guard. That job is identical to the baseline `codeql` job
apart from the guard and the language matrix, so a second lane
duplicates roughly 37 lines to express what deleting one line already
says.

The guard is also provably redundant for every trigger other than a fork
PR:

- `push`, `schedule`, `workflow_dispatch` — the first clause
`github.event_name != 'pull_request'` is already true.
- Same-repository `pull_request` — the second clause is already true.
- Fork `pull_request` — the job was skipped; it now runs. **This is the
only behavioural change.**

## Evidence that CodeQL actually works on a fork `pull_request` run

`bytefolk/digital-employee#250` is a fork PR (head
`PeterGuy326/digital-employee:feat/issue-245-memory-config`, sha
`abb2d58b29710927742c828a510c419c7c53efb8`). Its `codeql-fork` job
concluded `success` — check runs `102757987331` (84s) and `102757703884`
(86s).

That job declares `permissions: security-events: write` and contains
**no** `continue-on-error` on the job or on any step. A `success` job
conclusion therefore means every step succeeded, including `Analyze` —
and `github/codeql-action/analyze` fails with HTTP 403 when
`security-events: write` is absent. So SARIF upload from a fork
`pull_request` run is confirmed, not assumed.

Fork `pull_request` runs still receive no secrets and read-only
`contents`. All ByteFolk repositories are public.

## Tradeoff, stated plainly

After this change, fork PRs build untrusted code in a job holding
`security-events: write`. The run still has no secrets and read-only
`contents`, and every other required check in this repository (`Go`,
`Worker`, `Web`, `PostgreSQL integration`, `Web memory and transfer
acceptance`, `HTTP, CLI and MCP lifecycle`, `Workflow, scripts and
Compose`) already builds that same untrusted code, so this adds no new
class of exposure. It is the posture GitHub's own default CodeQL setup
takes for public repositories.

The alternatives were considered and rejected:

- `pull_request_target` would hand a write-scoped token to a workflow
run over attacker-influenced code.
- Self-reporting the three contexts through the Statuses API would
fabricate a required check that never ran.
- Granting contributors organization membership does not help at all:
the guard compares repositories, not author identity.

## What this does not touch

- No `permissions:` block changes. The job keeps exactly `contents:
read`, `actions: read`, `packages: read`, `security-events: write`.
- No required status context, branch protection rule or ruleset is
weakened or removed.
- No action SHA, trigger, matrix entry, step or job other than the
deleted line.
- The pinned `# v4.37.4` annotations are deliberately left alone. They
are the subject of bytefolk/.github#32 and #203, and this PR must stay
disjoint from them.

## Interaction with #203

#203 edits this same file, but only the three `github/codeql-action/*`
annotation lines at 58, 65 and 68, plus `bytefolk-scorecard.yml`. This
PR deletes line 36. The hunks do not overlap, so the two 3-way-merge
cleanly in either order. Neither needs rebasing because of the other.

## Checks

`Workflow, scripts and Compose` runs `actionlint` over every workflow.
Deleting a job-level conditional cannot introduce an actionlint finding;
the resulting YAML keeps `name`, `runs-on`, `timeout-minutes`,
`permissions`, `strategy` and `steps` on the `codeql` job. The release
pin validators in that job (`validate_release_action_pins.sh`,
`test_release_guards.sh`, `test_release_helpers_compat.sh`) all hardcode
`release.yml` and never read this file.

This branch is same-repository rather than a fork, so the PR's own three
CodeQL contexts can go green here — otherwise the fix could not
demonstrate itself.

## Follow-up

The identical guard sits in
`bytefolk/.github/workflow-templates/bytefolk-security.yml`, which is
where every consumer copied it from. A separate template PR is filed so
the fix propagates instead of regressing on the next template sync. Any
consumer that added its own `codeql-fork` lane (`digital-employee`, via
#250) must delete it once the template changes, otherwise two jobs
publish the same check name.

After this merges, #172 and #181 need a branch update rather than a
plain re-run: for `pull_request` events GitHub takes the workflow YAML
from the merge ref, while a re-run replays the workflow version captured
when the run was created. Both are already `state: behind` under
`strict: true`, so they need the update regardless.

Refs bytefolk/.github#35

Co-authored-by: 勒布朗-詹姆斯 <2986253039@qq.com>
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.

release(tag-path): verify v0.1.1 tag route and restore a sanctioned tag-cut path under ruleset 21899500

4 participants