Skip to content

Gate knowledge PRs the service did not open, and issue human-approved verdicts - #215

Merged
tzhouam merged 1 commit into
mainfrom
kb/external-knowledge-prs
Sep 28, 2026
Merged

tzhouam merged 1 commit into
mainfrom
kb/external-knowledge-prs

Conversation

@tzhouam

@tzhouam tzhouam commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator

Knowledge-service redesign follow-up. PRs the service didn't open now get a path to a signed verdict: human-written knowledge PRs (source ④), and changes outside the auto-merge whitelist via maintainer approval. Before this, both failed kb-gate forever.

  • external.poll_external (from the scheduler, holding the lease): judges each open, non-draft PR that touches knowledge/ and wasn't opened by the service, once per head and path.
    • human-approved: requires kb:human-approved and a knowledge maintainer's (CODEOWNERS of .github/kb-gate/) standing approval of the current head, read across all review pages. Then L1 on the governed pages, then a signed human-approved verdict.
    • auto: all paths must be governed. The full gate runs on current main plus the PR's changes; pre-images must match main, otherwise the author is asked to rebase. It needs a current judge calibration, as for the service's own PRs.
    • Anything else goes to people once per head. A head sent to people can move to the human path after approval, without a new push.
  • Verdict manifest: taken from git (the full merge-base..head diff), so non-governed paths are covered. Tests run the real kb-gate verifier on both verdict kinds.
  • Merge flow: external change sets use the normal flow.
    • A new head or a moved context is re-judged instead of going to people.
    • An author closing their own PR doesn't trip the overturn breaker.
    • Retirements and purges found by L1 enter the retirement ledger on merge.
  • Withdrawn approvals:
    • The service re-checks approvals on every pass; a withdrawal triggers a pause (dequeue + draft) and goes to people.
    • The change set keeps its queue slot until a pause issued after every possibly-live enqueue has expired is acked.
    • The verifier (bundle regenerated) now checks each reviewer's latest review, not only the bound one.

Commit hook approved; full suite, SPEC freshness and bundle check pass locally.

🤖 Generated with Claude Code

… verdicts

Two paths from the design that had no service side yet. Human-written
knowledge PRs (source 4) need a signed verdict from the gate. PRs that the
auto gate cannot pass (knowledge/tools, skills, ...) need a knowledge
maintainer's approval turned into a human-approved verdict. Until now every
such PR failed kb-gate forever.

- external.poll_external runs from the scheduler (lease held, not globally
  paused). It takes each open, non-draft PR that touches knowledge/ and was
  not opened by the service, and judges it once per head and path (auto or
  human), so a head sent to people on the auto path is judged again as soon
  as a maintainer approves it, without a new push:
  - kb:human-approved with a knowledge maintainer's (CODEOWNERS of
    .github/kb-gate/ on main) standing approval of the CURRENT head: L1 on
    the governed pages (the approval replaces the whitelist and L2, never the
    rest of L1), then a signed human-approved verdict bound to those reviews.
    A later review by the same person replaces an earlier one, read across
    every page of reviews;
  - otherwise, all paths governed: the full gate (L1, L2, consistency) on
    current main plus the PR's changes; a pass gets an auto verdict. The PR's
    pre-images must equal main's, or the author is asked to rebase;
    like the service's own PRs, an auto verdict needs a current passing
    judge calibration (calibration_required otherwise, re-judged only once
    calibration is current, so no repeated paid judge calls);
  - anything else goes to people once per head.
- The verdict manifest comes straight from git (the full merge-base..head raw
  diff), so it covers non-governed paths; context_base_sha is main at judging.
  Tests run the real kb-gate verifier on both kinds of verdict.
- An external change set follows the ordinary merge flow. A new head
  (head_changed) or a moved context (stale_context) is judged again at the
  next poll instead of going to people, and an author closing their own PR
  does not trip the overturn breaker.
- A withdrawn approval stops the PR. After signing, every pass re-checks that
  each named maintainer's latest review still approves the signed head; if
  not, it goes to people and a pause item dequeues it and makes it a draft.
  It keeps its status, and so the repository's queue slot, until that pause
  is acked (a failed pause is retried), and only then becomes
  approval_withdrawn. Until then it keeps holding the queue slot even if it
  was verdict_posted with an enqueue in flight, since it may have been queued
  meanwhile. A pause acked while an earlier signed enqueue could still run
  (the publisher may execute them in either order) does not complete it: the
  pause is repeated once that enqueue has certainly expired (plus a clock-skew
  margin). Completion is decided by when the acked pause was ISSUED (recorded
  on the pending item), never by when its ack arrived. The enqueue expiry is
  recovered at withdrawal from every durable record (the recorded value, the
  pending item, and unacked signed enqueue items still in the outbox), so a
  restart or a pre-upgrade change set cannot lose it. Remaining approvals
  never re-stage that head. The kb-gate verifier (bundle
  regenerated) now also checks each reviewer's latest review, not only that
  the bound review still reads APPROVED.
- External change sets carry no operations, so the rules L1 finds them
  retiring or purging are stored and entered in the retirement ledger on
  merge, like our own, so later release sweeps can purge them.
- KnowledgeRepo gains fetch_pull (a PR head fetched as data, never checked
  out), merge_base, show and raw_manifest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-28T15:49:52.665874Z b8dff3d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@tzhouam
tzhouam merged commit 1353815 into main Sep 28, 2026
5 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b8dff3d67e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +355 to +358
if head != changeset["head_sha"] and changeset["kind"] == "external":
# authors push: the new head is simply judged again at the next poll
rt.ledger.update_changeset(changeset["id"], status="head_changed", pending_item=None)
events.append(f"head_changed {changeset['id']}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Stop when an external pull request becomes draft

If an external PR is staged while ready and its author subsequently converts it to draft, this path only checks the head and continues signing and enqueueing it. Publisher._do_enqueue then calls gh pr ready for any draft PR (confirmed by gh pr ready --help: “Mark a pull request as ready for review”), so the service can undo the author's draft decision and merge work that was deliberately withdrawn from review. Re-check the draft state for external changesets and pause or retire them instead of applying the service-owned-PR resume behavior.

Useful? React with 👍 / 👎.

Comment on lines +164 to +169
for pr in _pulls(rt):
number = int(pr["number"])
head = str((pr.get("head") or {}).get("sha") or "")
labels = {str(label.get("name")) for label in pr.get("labels") or []}
human = HUMAN_LABEL in labels
if pr.get("draft") or not head or _ours(rt, number) or _judged(rt, number, head, "human" if human else "auto"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict external processing to pull requests based on main

For a PR targeting any branch other than main, the poller still judges its diff against current main and later enables auto-merge on the PR without inspecting pr["base"]. gh pr merge --help confirms that --merge merges into the PR's base branch, so such a PR can be recorded as merged—including retirement-ledger updates—even though its knowledge changes never reached the main branch used by activation and subsequent gates. Skip or send non-main-based PRs to people before staging them.

Useful? React with 👍 / 👎.

Comment on lines +368 to +369
if changeset["kind"] == "external" and changeset["detail"].get("source") == "human-approved" \
and changeset["status"] in ("verdict_posted", "queued"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recheck approval before signing the initial verdict

When a maintainer retracts approval after poll_external stages a human-approved changeset but before its first advance, the changeset is still pr_open, so this condition skips the approval check and signs a stale verdict. The verifier then rejects the comment and the merge flow moves the changeset to gate_failed; because that status is treated as already judged, restoring approval on the same head cannot recover it without another push. Include pr_open in the recheck and handle withdrawal before issuing the verdict.

Useful? React with 👍 / 👎.

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.

1 participant