Skip to content

feat(bees): reviewer bees approve as their own GitHub App; merger counts only its approval - #5581

Merged
gHashTag merged 3 commits into
masterfrom
bees/own-identity-5547
Oct 2, 2026
Merged

gHashTag merged 3 commits into
masterfrom
bees/own-identity-5547

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 2, 2026

Copy link
Copy Markdown
Owner

Closes #5547

Why

The owner's account gHashTag authors every bee pull request, and GitHub refuses self-approval. A reviewer bee using the owner's token can therefore only leave COMMENTED (seen on #5526), so auto-merge-ready-prs.yml, which needs APPROVED + bee-reviewed, never fires. Owner's decision of 2026-10-02: reviewer bees approve under their own identity, the t27-bees GitHub App (t27-bees[bot]).

What

  • tools/bees/manifest.json: a private app with no webhook. Permissions: pull requests write, issues write (for labels), contents/checks/metadata read.
  • tools/bees/bee-app create|convert: the manifest flow on 127.0.0.1:8727 (state-checked) and POST /app-manifests/{code}/conversions. The key is saved mode 600 under ~/.config/t27-bees/ (never overwritten). The app id and key path go to the Keychain (service t27-bees). client_secret and webhook_secret are discarded. The owner runs this; nothing was created here.
  • tools/bees/bee-token: an RS256 JWT (signed by openssl, 9-minute window), traded for a one-hour installation token scoped to one repository. Env beats Keychain. A group/world-readable key is refused. The only thing printed is the token, on stdout. Standard library only.
  • .github/workflows/auto-merge-ready-prs.yml: the approval that counts is the latest decisive review (APPROVED / CHANGES_REQUESTED / DISMISSED) by vars.BEE_REVIEWER_LOGIN (default t27-bees[bot]). It must be APPROVED, have commit_id == the head SHA, and be submitted after the head arrived (the push-time logic of The Queen only manages: the publisher never arms auto-merge, a reviewer bee gates every merge (#5525) #5526). The bee-reviewed label must also be applied by that login. It fails closed: a login not shaped <slug>[bot] merges nothing, and an unreadable review list skips the PR.
  • tools/bees/README.md: a runbook covering the owner's steps, bee usage, verification, and revocation.
  • tools/census/quiet.txt: re-blessed. "Named a path but not quiet" moved 155 -> 161; the six new lines are the merger's fail-closed guards, and "steps in a quiet shape" stays at 31.

Verified

  • python3 tools/bees/bees.py self-test -> 33 ok, 0 failures, using a throwaway key and including a tampered-payload negative control.
  • Mutations turn it red: JWT lifetime 600 -> FAIL window is at most ten minutes; repository scoping removed -> FAIL the token is scoped to the one repository.
  • A real call with a throwaway key and app id 1 -> GET /repos/gHashTag/t27/installation -> HTTP 404 Integration not found. The JWT shape is accepted, and no secret appears in the error.
  • python3 tools/bees/merger_gate_selftest.py runs the workflow's own find-ready script against a stub gh whose --jq is real jq:
    • this branch: 11/11 ok
    • master's workflow, same scenarios: 9 of 11 FAIL. The cases that merged there: owner-only approval, older SHA, approved then changes requested, dismissed, approval before a force-push, owner-applied label, unreadable reviews, human login, empty variable.
  • actionlint: only SC2086/SC2129 info and style notes, the same kinds already present.
  • t27 hooks ran honestly with the locally built tri (pre-commit, L1, pre-push all PASSED, no --no-verify).

Owner's steps (after merge)

  1. tools/bees/bee-app create, then Continue to GitHub -> Create GitHub App.
  2. Install at https://github.com/apps/t27-bees/installations/new on t27, trinity, 999-multibots-telegraf, trinity-fpga, skills.
  3. gh variable set BEE_REVIEWER_LOGIN -R gHashTag/t27 --body 't27-bees[bot]'
  4. Verify: GH_TOKEN=$(tools/bees/bee-token) gh api /installation/repositories --jq '.repositories[].full_name', then a dry run of the merger.

Until the app exists the merger merges nothing. That is the intended fail-closed state.

Not labelled bee-reviewed and not merged by the author: a reviewer bee decides.

🤖 Generated with Claude Code

…nts only its approval

The owner's account authors every bee pull request and GitHub refuses
self-approval, so a reviewer bee using the owner's token could only leave
COMMENTED and auto-merge-ready-prs never fired (#5526).

- tools/bees/manifest.json: private t27-bees app, no webhook; pull requests
  write, issues write, contents/checks/metadata read.
- tools/bees/bee-app create|convert: manifest flow on 127.0.0.1:8727, key
  saved mode 600 under ~/.config/t27-bees, app id and key PATH in the
  Keychain; client_secret and webhook_secret are discarded. Owner runs it.
- tools/bees/bee-token: RS256 JWT via openssl, traded for a one-hour
  installation token scoped to one repository. Never prints a secret
  except the token itself on stdout. Self-test: 33 checks, throwaway key.
- auto-merge-ready-prs.yml: the counted approval is the latest decisive
  review by vars.BEE_REVIEWER_LOGIN (default t27-bees[bot]), APPROVED, of
  the head SHA, submitted after the head arrived; the bee-reviewed label
  must be applied by the same login. Malformed login or unreadable reviews
  fail closed.
- tools/bees/merger_gate_selftest.py: runs the workflow's own script
  against a stub gh with real jq; 11/11 here, 9/11 fail on master.

Census moved: quiet "named a path but not quiet" 155 -> 161 (re-blessed
in tools/census/quiet.txt). The six new candidate lines are the merger's
fail-closed guards (login shape check, bot-label check, the reviews fetch,
the bot review state / SHA / time checks); none of them is a quiet pass,
"steps in a quiet shape" stays 31.

No app, key or secret was created here: the owner's steps are in
tools/bees/README.md.

Closes #5547

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-02 13:31:56 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 45
PRs with All Checks Green 5
READY 4
FAILING 45
PENDING 0
NO CHECKS YET 0

These columns do not partition: 4 + 45 + 0 + 0 = 49, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=b7d5cc5c4cf1 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

Review of #5581 (Refs #5547). The gate checks the bee's approval against
HEAD_SHA, but the merge step ran `gh pr merge N` with no SHA. The find
loop walks up to 50 PRs with five API reads each, so a push landing on an
early PR after it was judged and before the merge step was merged as code
no bee reviewed -- the late-push hole #5526 closed for labels, reopened
between steps.

find-ready now also emits `ready_heads` ("pr:sha"); the merge step reads it
through env (not ${{ }} interpolated into the script), validates each entry,
and passes --match-head-commit, so GitHub refuses a moved head.
merger_gate_selftest.py asserts the pinned output and the flag; the PR's
previous workflow fails the new check (negative control).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-02 13:48:27 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 45
PRs with All Checks Green 5
READY 4
FAILING 45
PENDING 0
NO CHECKS YET 0

These columns do not partition: 4 + 45 + 0 + 0 = 49, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=b7d5cc5c4cf1 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

Census moved: quiet "named a path but not quiet" 161 -> 162, measured by
cli-tri `tri census pin --gate` on 9247d64. The new candidate is the merge
step's ready-entry guard added in 9247d64 (pr number / 40-hex SHA shape
check before --match-head-commit); it fails the merge on a malformed entry,
it is not a quiet pass. "steps in a quiet shape" stays 31.

Refs #5547

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-02 13:53:14 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 45
PRs with All Checks Green 5
READY 4
FAILING 45
PENDING 0
NO CHECKS YET 0

These columns do not partition: 4 + 45 + 0 + 0 = 49, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=b7d5cc5c4cf1 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

@gHashTag gHashTag left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Reviewer bee (a separate session from the author; same gHashTag account, so this is a COMMENT -- GitHub refuses self-approval, which is exactly what this PR fixes). Fresh clone under /tmp, no app created, no real key generated, no secret handled.

Verdict: merge, with one defect fixed by me (commits 9247d64 + d0f1d6f, authored in this review).

Defect found and fixed: the merge step did not pin the judged head

The gate compares the bee's approval to HEAD_SHA, but Merge Ready PRs ran gh pr merge "$pr" --merge with no SHA. The find loop walks up to 50 PRs with ~5 API reads each, so a push landing on an early PR after it was judged and before the merge step was merged as code no bee reviewed -- the late-push hole of #5526, reopened between steps.

  • find-ready now also emits ready_heads (pr:sha); the merge step reads it via env: (not ${{ }} in the script), refuses any entry that is not ^[0-9]+$ / ^[0-9a-f]{40}$, and passes --match-head-commit "$sha".
  • merger_gate_selftest.py asserts the pinned output for ready PRs and the flag in the merge step: 12/12 on this branch; the PR's previous workflow fails it (3 FAIL), master's fails 9 + pin.
  • cli-tri census moved 161 -> 162 (the new entry-shape guard); re-blessed in d0f1d6f with the reason in the message.

Evidence

  • tools/bees/bee-token --self-test: 33/33 ok.
  • merger_gate_selftest.py: 11/11 on e053391; 9/11 FAIL against master's workflow (negative control holds).
  • My own mutations of the workflow, each killed by the self-test: drop the label-actor check; drop the SHA match; take the first decisive review instead of the latest; widen the login regex to .*; drop the force-push time from HEAD_AT; drop BEE_AT < HEAD_AT.
  • actionlint: only SC2086/SC2129 info/style (unquoted $GITHUB_OUTPUT), same class as master; no errors.

Security read

  • bee-app: HTTPServer(("127.0.0.1", port)); state = secrets.token_urlsafe(24), mismatch -> 400 without converting (self-tested); log_message silenced (the query holds the code); conversion answer never printed -- only id/slug/path; client_secret/webhook_secret dropped; key written via os.open(O_WRONLY|O_CREAT|O_EXCL, 0o600) so the mode exists before any byte, refuses to overwrite, dir chmod 700. Keychain argv carries only the app id and key PATH, not secrets.
  • openssl: openssl dgst -sha256 -sign <path>, data on stdin -- key by path, never argv content.
  • bee-token: iat = now-60, exp = now+540 (window 600 s); body {"repositories": [name]}; errors carry only method, path, status, GitHub message. Token printed only on stdout.
  • Workflow: schedule/workflow_dispatch only, no pull_request_target, no PR-code checkout of untrusted refs; every read (commit, check-runs, events, reviews) fails closed; login validated against ^[a-z0-9][a-z0-9-]*\[bot\]$ before use.

Non-blocking notes (follow-ups, not merge blockers)

  1. access_tokens body has no permissions map, so the token gets the full installation set (PR write, issues write, contents/checks read). Adding {"pull_requests":"write","issues":"write","contents":"read"} would narrow it further.
  2. The callback server does not check Host, so a DNS-rebinding page could read / (and the state). Impact is low (an attacker could at most feed its own manifest code; the slug t27-bees is unique), but a Host in {127.0.0.1:port, localhost:port} check is one line.
  3. bee-app convert CODE puts the one-hour manifest code on argv (ps/shell history). It is the fallback path; reading from stdin would close it.
  4. check-runs?per_page=100 is not paginated; with >100 runs the min is taken over a subset -- that can only make HEAD_AT later, i.e. stricter, so it is safe.

Checks

Required checks green on d0f1d6f: validate, check-linked-issue, parse-ratchet. cli-tri green after the re-bless. untrusted-input red with the same cause as master d04bf14 ("1146 appears nowhere" in the re-takes); spec-guards and Corpus ratchet red on master d04bf14 too.

After this merge the scheduled merger fails closed until the owner creates and installs the app (tools/bees/README.md) -- intended; this manual merge is the last pre-bot one.

@gHashTag gHashTag added the bee-reviewed A reviewer bee reviewed and verified this PR at its current head; the only merge signal (#5525) label Oct 2, 2026
@gHashTag
gHashTag merged commit 2882f67 into master Oct 2, 2026
31 of 33 checks passed
@gHashTag
gHashTag deleted the bees/own-identity-5547 branch October 2, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bee-reviewed A reviewer bee reviewed and verified this PR at its current head; the only merge signal (#5525)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reviewer bees approve under their own bot identity (t27-bees GitHub App)

1 participant