Skip to content

docs(governance): add GOVERNANCE.md release governance charter - #125

Closed
waterbro-8 wants to merge 7 commits into
bytefolk:mainfrom
waterbro-8:chore/add-governance-doc
Closed

waterbro-8 wants to merge 7 commits into
bytefolk:mainfrom
waterbro-8:chore/add-governance-doc

Conversation

@waterbro-8

@waterbro-8 waterbro-8 commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Refs #124

Consumed requirement revision: R4. (#124 is closed. R3 canonicalized the founder-approved
third code owner; R4 was the lifecycle correction. This supersedes the R2 revision previously
consumed by this body and by the charter.)

What this branch now contains

Two commits on top of the current main tip
87d235b7cffeb9d9e49653760b5362c792366900:

  1. d260021133b72d2e961cf7ad088330b5713565d8 — rebase of the reviewed head
    4a16bd838dcf24461be52f1f256ee8f6d103a357 (tree aac3f435d…). The 3-commit history,
    including one merge commit, collapsed to one; GOVERNANCE.md is untouched in this commit and
    stays blob f04fe109…, byte for byte as reviewed. The previously reviewed head remains
    fetchable by sha.
  2. 470babedf1ef6522ebc9de40f02f944f5dba8fcaa fast-forward on top of commit 1, not an
    amend, so the increment against the reviewed content is one file and nothing moved underneath
    it. This is the commit that fixes the charter wording, pushed separately so a reviewer can read
    it as the response to the standing review.

Both were performed by the pull request author's account (waterbro-8) through the Git Data API
(POST /git/blobsPOST /git/treesPOST /git/commitsPATCH /git/refs), because git
transport is unavailable from the host that performed them. Commit 1 preserves the original author;
commit 2 is authored and committed by waterbro-8.

Synchronization is provisional, not a claim of readiness. main advanced while this work was
in flight (4cf4452ee3e0, itself merged at 06:32:06Z today, → 87d235b7cf at 07:41:35Z, which adds
a step to the existing Web job plus one new script and touches none of the files this charter
links), so the branch was re-synchronized onto the newer tip. strict: true means this recurs: the
head that is finally merged must be brought up to date one more time immediately before merge and
must have fresh required CI on that exact head. Whoever performs that last sync is the last pusher
and should not also be the reviewer of it.

Per the rule stated on this PR — whoever supplies the new head does not review it — the author
supplied this head, so the reviewer who requested changes can review it. Bindy-lbb remains the
pending requested reviewer. Nothing in this edit is a review request, an approval, a merge, or a
close of #124.

Problem

The v0.1.0 release exposed two governance gaps: stable tags were repeatedly recreated while
repairing a broken release workflow, and otherwise-valid main protection did not apply to
administrators. Issue #81 had already solved the separate single-CODEOWNER problem; #124 records
the follow-up admin-enforcement decision and evidence, and its non-goals place the documentation
correction on this PR: "#125 remains a separate documentation candidate and must be corrected
independently to match the live configuration."

Goal and expected behavior

Add an additive GOVERNANCE.md charter that:

  • supplements, and does not replace or weaken, AGENTS.md and docs/maintainers/releasing.md;
  • records the live main protections it can evidence, including administrator enforcement, and
    says which of those values an administrator must re-read rather than take from this document;
  • requires a current non-author CODEOWNER approval while allowing the author or maintainer to
    perform the normal merge after all gates pass;
  • keeps published release tags immutable in normal and security-incident paths, and states what a
    gated tag creation means for the runbook;
  • defines the CODEOWNERS policy and defers the roster to .github/CODEOWNERS instead of
    duplicating it.

Non-goals: changing repository settings, CODEOWNERS, action pins, tags, releases, npm publication,
or runtime code. This PR changes no roles and no settings; GitHub's configuration stays the
mechanical source of enforcement.

Live state as re-read on 2026-09-03

What commit 2 corrected in the charter

  1. CODEOWNERS roster de-duplicated, and the two-owner claim removed. The section hardcoded
    * @PeterGuy326 @Bindy-lbb and asserted that keeping two owners was the maintainer decision
    with a third as "optional redundancy". It now states the rules (floor of two, non-author
    approval, roster change is a governance action that grants review authority only) and points at
    .github/CODEOWNERS as the source of truth. This also implements the maintenance suggestion
    left on this PR on 2026-09-02: duplicating the roster guarantees drift between the two
    documents.
  2. The drift hazard is named instead of hidden. The old text's rule — "any addition is a
    governance change to this document, not a silent edit to CODEOWNERS" — read as if chore(codeowners): add third independent reviewer #138 had
    violated policy, when it executed the R3-authorized change and this document was the lagging
    half. The section now says that plainly, including that landing the wording as a follow-up
    rather than in the same cycle "is the defect this section exists to close, not a precedent to
    repeat".
  3. Both tag rulesets recorded, with the consequence spelled out. The charter named one ruleset;
    it now names both, and states that tag creation on refs/tags/v* is itself restricted, so
    the v0.1.1 example and runbook step 4 must go through a permitted creation path, and a missing
    path is escalated through a reviewed ruleset change rather than bypassed or worked around. The
    document does not claim which ruleset predates the other, because GitHub exposes no ruleset
    creation timestamp.
  4. The required-check list no longer claims to be the configuration. It listed 8 names as the
    exhaustive strict set while 15 job names are observable on this repository. The bullet now states
    the policy (all required checks pass on an up-to-date head), marks the authoritative list as
    admin-readable, and presents the names as an observation dated 2026-09-03.
  5. Stale attribution refreshed: "Release governance gap: admin self-merge bypass and v0.1.0 tag churn (follow-up to #81) #124 revision R2" → the R2/R3/R4 record with the issue closed;
    "the matching … tag ruleset" (singular) → the two rulesets; "approval from the other
    CODEOWNER" → "another current CODEOWNER", which is what a three-owner roster needs; and the
    intro now carries an explicit "settings are authoritative, a stale line here is a documentation
    defect" clause.

Acceptance-criteria mapping

  • AC1 additive document boundary: GOVERNANCE.md introduction, unchanged by commit 2.
  • AC2 complete live branch-protection baseline: partial by design. Everything the charter
    asserts about protection is either a Release governance gap: admin self-merge bypass and v0.1.0 tag churn (follow-up to #81) #124-recorded read-back or is explicitly marked as
    admin-readable configuration that this document does not restate authoritatively.
  • AC3 immutable tags without incident bypass: now records both live rulesets and the gated-creation
    consequence. One open question is left to a human, not papered over: under ruleset 21899500
    with no bypass actors, which path is actually permitted to create the next v* tag. The charter
    says it must be a permitted path and that a missing path is escalated; it does not invent one.
  • AC4 correct review/merge semantics: Branch protection on main and Release-cut pull requests.
    The release-cut section is unchanged by commit 2.
  • AC5 owner decision and history: now met — R3/chore(codeowners): add third independent reviewer #138 recorded, roster deferred to the file, R2
    attribution corrected.

Configuration and validation ledger

  • Base (current main tip) this branch is synchronized to: 87d235b7cffeb9d9e49653760b5362c792366900
  • Head under review: 470babedf1ef6522ebc9de40f02f944f5dba8fca (tree 598e3ce57ee9…)
  • Previous head in this sequence, for reviewers comparing increments: 3c1fdff4968aaee78b44ad7dd3e934631a6ae7f4
  • GOVERNANCE.md blob: 0650fcebb18899810957fd21993971c62b2fe497 (136 lines, 7828 bytes),
    previously f04fe109579e72af34986977a84dd1be1f3ae476
  • Credentials required for the read-only checks below: repository metadata read only.
ID Exact step Expected Observed Status
V1 gh api repos/bytefolk/mem/branches/main/protection Every protection value asserted by the charter is matched to live settings 403 for waterbro-8; no branch ruleset alternative NOT RE-RUN — carried from #124 R2, and the charter now labels it as such
V2 gh api repos/bytefolk/mem/rulesets, then each ruleset by id Complete live tag configuration recorded Two active refs/tags/v* rulesets (21888356 update+delete, 21899500 creation), no bypass actors; both now recorded PASS
V3 Tree-level equivalence proof in place of git diff --check (no local git objects for these shas; git transport down on this host) Rebased tree = main tree + GOVERNANCE.md only; increment touches only the charter 699 main paths unchanged; added set exactly {GOVERNANCE.md}; diff of head tree vs rebase tree is that one path PASS
V4 gh api repos/bytefolk/mem/contents/<path>?ref=main for every relative link the charter uses All resolve AGENTS.md = 7144859e3, docs/maintainers/releasing.md = 4af8c0b97, .github/CODEOWNERS = 3-owner line PASS
V5 Re-scan charter wording against the live reads above; blob round-trip GET /git/blobs vs local file No stale or unsafe wording; file bytes as intended Round-trip identical (git hash-object agrees); no trailing whitespace, no tabs, no conflict markers, terminates with newline; the four stale statements removed PASS on the statements this method can reach
V6 git log -p style review of commit 2 against the previously reviewed head Only GOVERNANCE.md differs Enforced structurally by the tree comparison in V3, since both heads share parent 4cf4452ee3e0 PASS

What V5 cannot check, stated so nobody reads it as stronger than it is: whether the enumerated
check jobs are the required set (V1 blocks that), and which creation path is permitted for v*
tags under ruleset 21899500 (left open above rather than guessed).

Fresh required CI on the exact up-to-date head must be green before review; results belong to the
Checks tab and this body deliberately does not freeze a transient check state.

Risk, limitations, and rollback

This PR changes documentation only. It does not itself mutate branch protection or the tag
rulesets; GitHub settings remain the mechanical source of enforcement, and drift must be corrected
without weakening the stronger rule. Remaining limitation: parts of the branch-protection baseline
are unreadable to non-administrators, so the charter flags them as records of a #124 read-back
instead of restating them as verifiable fact. Rollback of commit 2 alone is a normal revert of that
one commit and leaves the synchronized branch intact. Commit 1's only parent is the main tip named
above, so this branch contains no content beyond that base plus GOVERNANCE.md.

CHANGELOG

Not updated: this is an internal repository-governance charter with no runtime or user-facing
product behavior change.

Automated assistance

Codex assisted the maintainer with reconciling the charter against the live settings and validation
evidence, and the 2026-09-03 rebase, charter correction, and this body rewrite were executed by the
author's agent through the Git Data API. The human PR author and maintainers remain accountable; no
automated tool is an author or co-author.

@PeterGuy326 PeterGuy326 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking changes required before this governance charter can be treated as enforceable policy:

  1. The live main protection has enforce_admins.enabled: false; this PR currently states enforce_admins: true and that settings enforce the charter mechanically as present facts. Either land the separately authorized narrow setting change first, or describe this as a pending target and keep the live/enforced state explicit. No setting change is authorized by this PR.
  2. Preserve every stronger live gate in the written baseline: strict: true, require_last_push_approval: true, required_linear_history: true, required_conversation_resolution: true, plus force-push/deletion disabled. The full replacement PUT in #124 would accidentally disable/omit three of these; remove it from #124 and, if separately authorized, use only POST .../protection/enforce_admins with before/after field-for-field verification.
  3. Remove every security-incident exception that permits disabling tag protection or moving/deleting/reusing a published tag. Published tags remain immutable evidence. Respond through an advisory, channel deprecation/yank where supported, a reviewed fix, and a new patch tag.
  4. Correct the history and terminology: #81 fixed the structurally unsatisfiable single-CODEOWNER setup; this is a follow-up admin-bypass gap. Prohibit admin bypass and merges without a current independent approval, rather than prohibiting the author from pressing the merge button after a valid independent approval. The recent-five evidence is #117/#115/#114/#108/#105 (not #93).
  5. Link this PR with Refs #124 so policy CI can identify the canonical issue, but do not mark #124 ready/closed until the owner decides enforce-admins and the two-versus-three CODEOWNER tradeoff. Add cross-references to AGENTS.md and docs/maintainers/releasing.md and describe this charter as additive.

The stable-tag ruleset already exists and must remain unchanged. I will re-review a corrected exact head; the current head 98126e69a1513b2852129aa0568779e65938fcdd is not approvable.

PeterGuy326
PeterGuy326 previously approved these changes Aug 31, 2026

@PeterGuy326 PeterGuy326 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of exact head 4a16bd838dcf24461be52f1f256ee8f6d103a357: approved.

All five blockers from my review of 98126e69a1513b2852129aa0568779e65938fcdd are resolved:

  1. The charter now matches the separately applied live enforce_admins: true state.
  2. It preserves every stronger gate: strict checks, last-push approval, linear history, conversation resolution, and force-push/deletion prohibitions.
  3. Security incidents have no tag-protection bypass; published tags remain immutable evidence.
  4. #81 history, recent-five evidence, admin-bypass terminology, and normal post-approval merge semantics are corrected.
  5. The PR references #124 R2 and is explicitly additive to AGENTS.md and the release policy.

Independent read-only final audit found no governance-content blocker. All 15 current checks pass. The live branch-protection object and tag ruleset 21888356 were also read back and match the document field-for-field.

Because I made the latest maintainer push, this review does not satisfy require_last_push_approval; @Bindy-lbb must independently approve this same exact head before a normal merge. No admin bypass is authorized.

@PeterGuy326 PeterGuy326 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested on exact head 4a16bd838dcf24461be52f1f256ee8f6d103a357.

The required CI from this head is historical, not merge-valid: the branch is behind current main, so strict up-to-date protection requires a new candidate and fresh exact-head checks.

More importantly, the charter and PR narrative still state that the repository chose to retain two CODEOWNERS. That is superseded by the canonical owner decision in #124 R3, which authorizes the bounded third-owner change in #132. Update the text to distinguish current/merged state from the approved #132 transition, preserve all existing protection and tag-authority boundaries, and avoid claiming that #125 changes roles or settings.

After #132 is normally merged (not bypassed), rebase or otherwise non-destructively synchronize #125 against that mainline, update its evidence to the resulting exact head, rerun required CI, and request independent review. If the author supplies the new head, I can review it; if I supply it, @Bindy-lbb must review instead.

@sun-970

sun-970 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

One maintenance suggestion before this charter is finalized.

The CODEOWNERS section hardcodes a snapshot that will drift

The ## CODEOWNERS section currently embeds the literal owner list:

* @PeterGuy326 @Bindy-lbb

This means every future owner change (and #138 is already adding a third) requires a coordinated update to GOVERNANCE.md as well. If the two documents drift apart, readers won't know which one is authoritative — and the charter's own rule ("any addition is a governance change to this document, not a silent edit to CODEOWNERS") makes that drift look like a policy violation even when it's just a stale reference.

Suggestion: reference the source of truth instead of duplicating it

Replace the inline code block with a statement like:

The current CODEOWNERS are defined in .github/CODEOWNERS. The charter's review and merge rules apply to whoever is listed there at the time of each PR.

Then keep the policy paragraph (non-author approval, availability rationale, addition requires a governance change) but drop the literal names. That way:

  • The charter describes rules, not a roster.
  • Adding or removing an owner is a single-file change in .github/CODEOWNERS, and the charter automatically stays accurate.
  • The "two owners minimum; third is optional redundancy" decision can be recorded as a historical note (e.g. "as of , the owner set was expanded from two to three via chore(codeowners): add third independent reviewer #138") without hardcoding the result.

This also sidesteps the current blocker from the third review: the sentence "the maintainer decision is to keep these two owners" is already stale because #124 R3 authorized a third. Decoupling the reference from the policy would prevent this class of staleness from recurring.

@waterbro-8
waterbro-8 force-pushed the chore/add-governance-doc branch from 4a16bd8 to 1307fef Compare September 3, 2026 07:11
PeterGuy326 and others added 2 commits September 3, 2026 07:58
Refs bytefolk#124

The charter previously asserted a two-owner CODEOWNERS roster and a single tag
ruleset. A third owner reached main through bytefolk#138 under the bytefolk#124 R3 decision, and
refs/tags/v* is covered by a second active ruleset that also blocks creation.
De-duplicate the roster against .github/CODEOWNERS, record both rulesets and what
a gated tag creation means for the runbook, replace the exhaustive required-check
claim with an observed-jobs enumeration, and refresh the R2 attribution to R4.
@waterbro-8
waterbro-8 force-pushed the chore/add-governance-doc branch from 3c1fdff to 470babe Compare September 3, 2026 07:59
@waterbro-8

Copy link
Copy Markdown
Collaborator Author

Author-side status update on the standing changes-requested review. This is not an approval, not a
request for review, and not an acceptance record — it only states what the new head contains and
what still needs a human decision.

Head under review: 470babedf1ef6522ebc9de40f02f944f5dba8fca (tree 598e3ce57ee9…), base
87d235b7cffeb9d9e49653760b5362c792366900. Required CI: 15/15 check runs completed success on
this exact head (suites 91410172427, 91410172748, 91411493014).

The work is two commits, deliberately separated so the increment against the reviewed content is
readable on its own:

  • d260021133 — rebase only. GOVERNANCE.md is byte-for-byte the blob f04fe109… that was already
    reviewed; the 3-commit history including one merge commit collapsed to one.
  • 470babedf1 — fast-forward on top of it, the only file touched being GOVERNANCE.md
    (now blob 0650fcebb1, 136 lines, +136/−0 against main).

Mapped to the five points from the review of 4a16bd838:

  1. The two-CODEOWNERS claim is gone. ## CODEOWNERS no longer reproduces the roster; it states
    the rules and defers the name list to .github/CODEOWNERS, so a future owner change cannot leave
    the charter asserting a roster that no longer exists. The Release governance gap: admin self-merge bypass and v0.1.0 tag churn (follow-up to #81) #124 R3 decision and the fact that the
    third owner reached main through chore(codeowners): add third independent reviewer #138 are recorded, and the section says outright that landing
    the charter wording as a follow-up rather than in the same cycle is the defect being closed, not
    a precedent to repeat. This also implements the maintenance suggestion left on this PR on
    2026-09-02 from the opposite direction.
  2. Both live tag rulesets are now recorded21888356 (update + deletion) and 21899500
    (creation), both active on refs/tags/v* with no bypass actors — together with what that means
    for the runbook: cutting a tag is itself a restricted action, and a missing permitted creation
    path is escalated through a reviewed ruleset change, never by disabling or bypassing a rule. The
    document does not claim which ruleset predates the other, because GitHub exposes no ruleset
    creation timestamp.
  3. No claim that this PR changes roles or settings. Stated in Goal/non-goals, and the intro now
    carries "settings are authoritative; a stale line here is a documentation defect".
  4. Protection baseline is no longer presented as exhaustive. The required-check bullet states
    the policy, marks the authoritative list as admin-readable, and labels the 15 observed job names
    as an observation dated 2026-09-03. enforce_admins: true is labeled as Release governance gap: admin self-merge bypass and v0.1.0 tag churn (follow-up to #81) #124's read-back rather
    than a re-verification, because GET /repos/bytefolk/mem/branches/main/protection returns 403 for
    this account and there is no branch ruleset to read instead. AC2 is therefore recorded as
    partial by design, not as satisfied.
  5. Evidence ledger refreshed to the exact heads named above, including what each check could and
    could not reach: git diff --check was replaced by a tree-level proof (699 main paths
    unchanged; the added set is exactly {GOVERNANCE.md}) because git transport is unavailable from
    the host that performed this; the relative links were re-read on main; and the two things this
    method cannot check are named as such.

One question this branch does not answer, and should not paper over: under ruleset 21899500
with no bypass actors, which path is actually permitted to create the next v* tag? v0.1.1 exists
from 2026-08-31T06:32:10Z, but GitHub exposes no ruleset timestamps, so nobody here has established
whether that path still exists. If it does not, "cut a new patch tag" in the runbook is currently
unexecutable, which is a repository-configuration question rather than a wording question.

Two process notes: main advanced while this was in flight, so with strict: true the branch needs
one more non-destructive sync immediately before merge and fresh CI on that final head — and whoever
performs that sync is the last pusher, so should not also be its reviewer. And per the rule stated on
the review, the author supplied this head, so the reviewer who requested changes can review it;
Bindy-lbb remains the pending requested reviewer. No merge, close, label, assignee, review vote, or
review request on this PR or #124 came from this account during this work.

@PeterGuy326

Copy link
Copy Markdown
Collaborator

Read-back complete — all five blocking items verified in the diff: CODEOWNERS now references .github/CODEOWNERS with the #124#138 three-owner history written factually; both refs/tags/v* rulesets recorded incl. 21899500; required checks marked as a 2026-09-03 observation rather than a complete list; enforce_admins documented as the #124 read-back with the non-admin 403 boundary; R2→R4 attribution corrected. The 'what this method cannot test' ledger is right; no further content asks.

Decisions:

  1. The 21899500 tag-path question is a repo-config issue, not a charter-text issue — filed separately (companion issue, owner handles personally as a v0.1.2 pre-release action). Charter text stands as written; do not amend it for this.
  2. Do NOT close docs(governance): add GOVERNANCE.md release governance charter #125 yet. Flow: rebase once more onto current main → my CHANGES_REQUESTED is dismissed with this read-back (executed now) → Bindy-lbb completes review → normal squash merge (no admin bypass) → close docs(governance): add GOVERNANCE.md release governance charter #125 after merge.
  3. Your discipline this round (no self-approve, no merge, silence ≠ approval) is exactly the standard — keep it.

@PeterGuy326
PeterGuy326 dismissed their stale review September 3, 2026 08:28

Read-back verified: all five blocking items addressed per the 2026-09-03 ledger. Unblocking for Bindy-lbb review; merge via normal squash flow.

@waterbro-8

Copy link
Copy Markdown
Collaborator Author

Read-back of the sync step in the 2026-09-03 flow note (item 4, first step). Not a review, carries no approval or request-changes, and is not a merge, close, or review-request action. It exists because that step turns out to be already satisfied, and the reason this PR is still blocked is not the one the flow assumed.

1. Sync state: already on current main, nothing pushed

read (live, at time of writing) value
main tip 87d235b7cffeb9d9e49653760b5362c792366900, committed 2026-09-03T07:41:35Z — unchanged since
this PR's base.sha 87d235b7cffeb9d9e49653760b5362c792366900 — same commit
compare/main...470babedf1 status=ahead, ahead_by=2, behind_by=0
head tree vs main tree, blob-level added exactly [GOVERNANCE.md], removed [], changed []
GOVERNANCE.md blob at head 0650fcebb18899810957fd21993971c62b2fe497, 7828 bytes / 136 lines — byte-identical to the value recorded in the body's ledger

There is therefore no rebase to perform at this moment, and the charter text has not moved, which is what item 3 asked for. No ref was touched by this check: pushing an empty "sync" would change only who last pushed, and the body already states that the last pusher should not also be the reviewer of that push.

The merge-time rule from the body still stands and is not closed by the above: if main advances before Bindy-lbb finishes, the head must be brought up to date one more time immediately before the squash merge, with fresh required CI on that exact head.

2. Required CI on this exact head: 15/15 success

GET /commits/470babedf1ef6522ebc9de40f02f944f5dba8fca/check-runs returns 16 records over 15 distinct job names (the 16th is a superseded round of Conventional title and linked issue on the same head; the current round is success). Taking the highest check_suite.id per name: suites 91410172427, 91410172748, 91411493014, all completed / success. statusCheckRollup is empty for this head — a known aggregation quirk on this repository, so the rollup is not the reading to trust here.

3. Why it is still blocked: the dismissal has not landed

The flow note said CHANGES_REQUESTED was dismissed. Two independent endpoints disagree:

review id author state read now submitted head it reviewed
5066232814 PeterGuy326 CHANGES_REQUESTED 2026-08-31T12:08:11Z 4a16bd838dcf24461be52f1f256ee8f6d103a357
5062515904 PeterGuy326 DISMISSED 2026-08-31T01:36:07Z 98126e69a1…
5062950577 PeterGuy326 APPROVED 2026-08-31T03:43:36Z 1307fef739…

GET /pulls/125/reviews and GraphQL pullRequest(number: 125) { reviewDecision }CHANGES_REQUESTED agree. Because 5062515904 in the same response reads DISMISSED, this endpoint does surface dismissals on this repository — so 5066232814 is genuinely still in force, not a lagging projection.

Consequences worth stating plainly:

  • Step 2 of the flow is the open item, not step 1. The sequence rebase → dismissal → Bindy-lbb review → squash merge → close cannot be read in order from here: step 1 was already satisfied and is a no-op, while step 2 did not take effect. Dismissing 5066232814 is the reviewer's action; nobody else should substitute for it, and dismissing it on his behalf would be exactly the thing this charter is written to forbid.
  • After a successful dismissal, reviewDecision may land on the 2026-08-31 APPROVED, which was submitted against 1307fef739 — not this head. The charter's own rule is a current non-author CODEOWNER approval, so an approval on a stale head is not the gate this document describes. Bindy-lbb remains the pending requested reviewer; nothing here is a nudge to him.
  • mergeable_state = blocked is consistent with both causes above. It cannot be decomposed further from here: GET /branches/main/protection is 403 for this account (non-admin), so the configured gate set is Release governance gap: admin self-merge bypass and v0.1.0 tag churn (follow-up to #81) #124's carried read-back, not a fresh read.

4. Ledger drift in the body, flagged instead of edited

The body pins Base (current main tip) this branch is synchronized to: 87d235b7… and Head under review: 470babed…. Both are still literally true right now. This comment is where the state is re-read; the body has not been edited, so nothing is silently rewritten after the review that was performed against it.

5. What this check did and did not do

Did: read-only API calls (git/ref/heads/main, pulls/125, compare, check-runs, reviews via REST and GraphQL, git/trees of both heads, branches/main/protection).

Did not: push or rebase anything, edit GOVERNANCE.md, edit this PR's body, dismiss or un-disqualify any review, request a review, merge, close #125, act on #124, or touch #163 (the tag-creation path, which item 3 of the flow note moved to the repository-config side with its own owner).

@PeterGuy326
PeterGuy326 dismissed stale reviews from themself September 4, 2026 06:14

Withdrawing at author's request: the five blocking items from this round were all addressed in d260021 + 470babe (CODEOWNERS reference, both rulesets recorded, required-checks labelled as observed, enforce_admins attributed as read-back, R2→R4 attribution). The intended withdrawal on 08-31 did not land on GitHub; this dismiss is that action. Refs #125.

@PeterGuy326 PeterGuy326 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent review: this is documentation-only governance text. It defers the CODEOWNERS roster to the source-of-truth file, records the tag rulesets as evidence rather than enforcement, and states the non-author review requirement without changing settings. Required CI is green and the branch contains current main. Approving for squash auto-merge.

@PeterGuy326
PeterGuy326 enabled auto-merge (squash) September 6, 2026 03:14
@waterbro-8 waterbro-8 closed this Sep 8, 2026
auto-merge was automatically disabled September 8, 2026 06:06

Pull request was closed

@waterbro-8 waterbro-8 reopened this Sep 8, 2026
@waterbro-8

Copy link
Copy Markdown
Collaborator Author

Author-side post. Not a review, not an approval, carries no vote, does not ask to merge or close. I am the author of this PR, so nothing here counts as review evidence for it; the review this PR still needs is Bindy-lbb's (still on requested_reviewers), and PeterGuy326's APPROVED at 2026-09-06T03:13:48Z is on the current head 713cd66891dd.

Raising this because of bytefolk/mem#172: it adds the same file this PR adds, with different content, and main has neither. Detail in my comment there; the numbers, measured at 2026-09-10:

My position as author, so @sun-970 has something to decide against rather than a stale question: I am not defending this file's scope. If #172 is the version the maintainers want, tell me which of the six sections here (Motivation · Branch protection on main · Tag immutability · Release-cut pull requests · CODEOWNERS · Incident runbook) still needs a home and I will either move them into your file or narrow this PR to just those. I would rather land a smaller charter than have two half-overlapping ones. I have touched neither branch and will not touch #172.

One mechanical fact for whoever syncs this branch next, since mergeable_state = behind (base 2986fe38175f, compare/main...head = diverged / ahead 3 / behind 2, merge base 7a194f1eba4167d54bd46cf84cdbe86e00532319): a sync makes whoever triggered it the branch's last pusher, and that retires an approval already sitting on the head — I watched exactly that happen to mem#140 on 09-07, where an APPROVED turned DISMISSED within seconds of my own push landing on it. So syncing here costs PeterGuy326's existing approval. If #172's outcome is going to replace most of this file anyway, syncing is wasted work — settle the overlap first.

For the record on CI, so nobody has to re-derive it: 17 check names on this head, 16 completed / success, and the 17th is CodeQL (${{ matrix.language }}) = completed / skipped — the unexpanded matrix title in the name is itself the sign that this branch carries a pre-#167 copy of bytefolk-security.yml. Dependency review does run and pass here (suite 92623237699).

@PeterGuy326
PeterGuy326 enabled auto-merge (squash) September 10, 2026 16:07
@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 waterbro-8/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

Superseded by #204, which carries this charter onto a branch inside bytefolk/mem.

Why this pull request cannot merge, and it is not your fault. Its head branch lives in
waterbro-8/mem. .github/workflows/bytefolk-security.yml:36 guards the codeql job with:

if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}

Skipping CodeQL on a fork PR is correct — the job needs security-events: write to upload
SARIF, which GitHub does not grant to a pull_request run from a fork. The problem is what
the skip produces. Because codeql is a matrix job skipped by a job-level if:, GitHub
never expands the matrix and publishes the check run under the literal, un-interpolated
name CodeQL (${{ matrix.language }}) with conclusion SKIPPED. That string matches none
of CodeQL (go), CodeQL (javascript-typescript), or CodeQL (python), all of which
main requires. A required context that is never reported is treated as pending forever.

So no number of approvals makes this mergeable, and Update branch, auto-merge, and
re-running workflows cannot help either. Closing it is not a judgement on the charter.

Confirmed by contrast. #204 is the same document on a same-repo branch, and within a
minute of opening it reported CodeQL (python) as success with CodeQL (go) and
CodeQL (javascript-typescript) running under their correctly interpolated names. Same
protections, same required contexts, only the head repository differs.

One separate thing worth recording. The APPROVED review from 2026-09-06 was mine
(PeterGuy326), not yours, and it is no longer in effect. dismiss_stale_reviews: true and
require_last_push_approval: true are both set on main, and four Merge branch 'main'
commits landed on the head afterwards, the last at 2026-09-10T16:32:33Z. Those were also
mine — maintainer_can_modify is true on this pull request, which is how I was able to
push to your fork branch to keep it current. review_decision is currently empty. So even
setting the fork problem aside, this pull request was also waiting on a re-approval, and
keeping it up to date was actively destroying the approval it had. #204 needs an approval
from a non-author code owner too; I cannot supply it, since I authored #204.

What changed in the content. The charter itself carries over, but four claims in its
Tag immutability and Incident runbook sections did not match live configuration and are
corrected in #204. The load-bearing one: the charter said neither refs/tags/v* ruleset
has bypass actors. 21899500 (Restrict stable release tag creation) has exactly one —
repository role admin, bypass_mode: always. 21888356 (Protect stable release tags)
has none, which is the rule that actually makes published tags immutable. v0.1.1 proves
the admin creation bypass: it is tagged 2026-08-31T06:32:10Z, 1h45m after 21899500
became active, by a repository admin. Incident runbook step 4 repeated the same claim, so
as drafted it would have told a maintainer mid-incident to escalate a tag cut an admin can
perform directly.

The likely source of the error is that the collection endpoint
GET /repos/{owner}/{repo}/rulesets renders bypass_actors as null for every ruleset;
only GET /repos/{owner}/{repo}/rulesets/{id} shows the truth. Your commit 470babed
tracking the live CODEOWNERS policy and both rulesets is preserved via Co-authored-by on
#204. The four corrections are mine, not yours.

If you would rather own this yourself: you have write access, so you can push your
branch to bytefolk/mem directly and open the pull request from there. Say the word and I
will close #204 and leave the field to you. The fork branch is not the problem — only
having the head inside this repository is what lets CodeQL report.

Closing as superseded.

PeterGuy326 added a commit that referenced this pull request Sep 11, 2026
…cted (#204)

## What this does

Adds `GOVERNANCE.md`, the release governance charter for this
repository: branch
protection on `main`, `refs/tags/v*` immutability, the stricter bar for
release-cut pull
requests, the CODEOWNERS policy, and an incident runbook.

It is additive. It does not change any workflow, setting, or rule, and
it explicitly
subordinates itself to live repository configuration.

## Why this is a new pull request rather than #125

#125 carries the same charter, but its head branch lives in a fork
(`waterbro-8/mem`). `.github/workflows/bytefolk-security.yml:36` guards
the `codeql` job
with:

```yaml
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
```

`codeql` needs `security-events: write` to upload SARIF, which GitHub
does not grant to a
`pull_request` run from a fork, so skipping it there is correct. The
problem is what
skipping produces. Because `codeql` is a matrix job skipped by a
job-level `if:`, GitHub
never expands the matrix and publishes the check run under the literal,
un-interpolated
name `CodeQL (${{ matrix.language }})` with conclusion `SKIPPED`. That
name matches none of
`CodeQL (go)`, `CodeQL (javascript-typescript)`, or `CodeQL (python)`,
which `main`
requires. Required contexts that are never reported are treated as
pending forever.

So #125 cannot merge at any number of approvals. `Update branch`,
auto-merge, and
re-running workflows all fail to change that. Re-pointing the work onto
a branch inside
`bytefolk/mem` is the only resolution that needs no policy change and
grants no new
permission, which is why this pull request exists.

#125 should be closed as superseded by this one.

## What differs from #125's content

The charter is otherwise the same document, but four claims in its Tag
immutability and
Incident runbook sections did not match the live configuration. They are
corrected here.

**1. "two active rulesets, neither of which has bypass actors" —
false.**

| Ruleset | Blocks | Bypass actors |
| --- | --- | --- |
| `21888356` Protect stable release tags | `update`, `deletion` | none |
| `21899500` Restrict stable release tag creation | `creation` |
repository role `admin` (`repositoryRoleDatabaseId` 5), `bypass_mode:
always` |

Read from `GET /repos/bytefolk/mem/rulesets/{id}`, and confirmed via
GraphQL
`RepositoryRulesetBypassActor.repositoryRoleName`, which returns the
string `admin`
directly rather than an id that has to be interpreted.

The asymmetry is deliberate and is now stated as such: creation stays
reachable so a
release can always be cut, while published tags are immutable for every
role including
admin. Immutability is enforced by `21888356`, not by `21899500`.

**2. "GitHub exposes no creation timestamp for rulesets" — false.**

The individual ruleset endpoint returns `created_at` and `updated_at`,
so the pair is
orderable: `21888356` at `2026-08-31T00:34:47Z`, `21899500` at
`2026-08-31T04:47:11Z`
(updated 54 seconds later at `04:48:05Z`). The charter previously
declined to say which
rule came first on the strength of that incorrect premise.

**3. "If no such path exists when a release is needed, that is a
blocker" — a path exists.**

A repository admin can cut a `v*` tag directly. `v0.1.1` demonstrates
it: annotated tag
object `c2ecc1c49ff8bbe13b9d7800bc910e4b7ac99b74` dereferences to commit
`cc727db0bc72655f299166de1f60756f5c686cc7` and is tagged
`2026-08-31T06:32:10Z`, one hour
and forty-five minutes after `21899500` became active, by a repository
admin.

**4. Incident runbook step 4 repeated claim 1.**

This is the one with operational consequences. As drafted, a maintainer
working a
compromised-release incident at 3am would have read that tag creation
"has no bypass
actors" and that a missing creation path "is escalated, not worked
around" — and would
have escalated a tag cut that a repository admin can simply perform. The
step now says it
is an admin action that requires no ruleset change, and that no ruleset
change should be
made in order to perform it.

**Probable cause of the error, now recorded in the document.** The
collection endpoint
`GET /repos/{owner}/{repo}/rulesets` renders `bypass_actors` as `null`
for every ruleset.
A reader who trusts that rendering concludes no bypass actors exist
anywhere. The
corrected section names the per-ruleset endpoint explicitly so the
mistake is harder to
repeat.

## What was verified and left alone

All nine branch-protection values the charter asserts were re-read from
`GET /repos/bytefolk/mem/branches/main/protection` and are correct as
written, so that
section is unchanged:

`enforce_admins: true` · `required_approving_review_count: 1` ·
`require_code_owner_reviews: true` · `dismiss_stale_reviews: true` ·
`require_last_push_approval: true` · `required_linear_history: true` ·
`required_conversation_resolution: true` · `allow_force_pushes: false` ·
`allow_deletions: false` · `bypass_pull_request_allowances: null`

One sentence was added to that section to keep the two controls
distinct: branch
protection having no bypass actors is a separate fact from tag creation
having an admin
bypass, and conflating them is what makes claim 1 plausible on a first
read.

The required-check enumeration in that section is unchanged and remains
explicitly hedged
as "evidence of what runs, not a substitute for the configuration". It
is dated 2026-09-03
and does not list `CodeQL (go)`, `CodeQL (javascript-typescript)`,
`CodeQL (python)`, or
`Dependency review`, all of which are in fact required on `main` today.
The hedge already
covers this, but a reviewer who wants the list refreshed should say so
rather than let it
stand as a near-miss.

## Attribution

The charter's first commit (`d2600211`, aligning it with the enforced
protections) and the
`main`-sync merges are PeterGuy326's. The commit tracking the live
CODEOWNERS policy and
both tag rulesets (`470babed`, 2026-09-03) is waterbro-8's and is
credited via
`Co-authored-by`. The four corrections above are mine and are not
attributed to waterbro-8.

## Checks

- Docs-only. Adds one file, `GOVERNANCE.md`. No code, workflow,
configuration, or
  dependency change, so no runtime behaviour is affected.
- Title is Conventional and the body links `#124` and `#125` for
`pr-policy`.
- Head branch is inside `bytefolk/mem`, so `codeql` is not skipped and
the three required
  CodeQL contexts will be reported normally.

Refs #124
Refs #125

Co-authored-by: waterbro-8 <318569545+waterbro-8@users.noreply.github.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.

3 participants