Repository navigation
Re-sign or rebuild knowledge PRs whose verdict went stale - #214
Conversation
A knowledge PR used to stay in verdict_posted forever once its signed verdict
stopped verifying: when it neared its 72-hour issue window, when main moved
the context pages under it, or when a merge group dropped it from the queue.
- merge.advance records when each verdict was signed (verdict_issued_at) and
when the PR was queued (queued_at).
- A posted verdict older than 48 hours goes back to pr_open for a fresh
verdict on the same head. A queued PR (with an ageing verdict, or queued 2
hours without merging) never frees its queue slot on a timer: a pause item
dequeues it and makes it a draft, and only that ack returns it to pr_open
for re-signing and re-enqueueing. A verdict_posted change set with an unexpired
enqueue outstanding is never re-signed; that ack decides. Such a change
set also holds the repository's single queue slot, like a queued one.
- A failing PR-stage kb-gate is classified by its description (the
verifier's first problem):
- only statuses posted after the current verdict count;
- transient (hold list, unreachable, verifier error, the kb:hold our own
pause left): wait;
- verdict expired or missing: re-sign;
- context changed, consistency pages moved, no clean merge, manifest
mismatch: rebuild. The same operations are re-applied to current main
and gated again as a "rebuild" change set (its evidence carries a
rebuild-of mark written atomically with staging, so an interrupted
rebuild resumes instead of staging twice). Only a rebuild that PASSES the
gate replaces the PR: the old change set waits in superseding, re-issuing
its close item when one expires, until GitHub shows the PR closed, and is
then marked superseded (it does not count as overturned by people). A
failing rebuild, operations that no longer apply, or a mechanical change
set with none go to people as rebuild_failed, with the old PR left open.
Only the scheduler, holding the lease, rebuilds;
- anything else (a real problem with the change): gate_failed, to people
once.
- Re-signing and superseding are traced as outcomes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 36841ec2cc
ℹ️ 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".
| _request_close(rt, lifecycle.repo, rt.ledger.changeset(changeset["id"])) | ||
| publish(rt, lifecycle, new_id) |
There was a problem hiding this comment.
Open the replacement before closing the original PR
When publish() cannot create the replacement—such as when the current judge calibration is missing/outdated, the service has switched to shadow mode, or the publisher later rejects open_pr—the close item has already been issued and is always executable. This closes the original PR while the replacement becomes calibration_required, shadow_recorded, or failed, leaving no replacement PR; publish and confirm the rebuilt PR before requesting closure of the original.
Useful? React with 👍 / 👎.
| signed_at = float(changeset["detail"].get("verdict_issued_at") or 0) | ||
| stale_verdict = bool(signed_at) and rt.clock() - signed_at > RESIGN_AFTER | ||
| queued_at = float(changeset["detail"].get("queued_at") or 0) | ||
| if changeset["status"] == "queued" and ( | ||
| stale_verdict or (queued_at and rt.clock() - queued_at > QUEUE_STALL)): |
There was a problem hiding this comment.
Initialize timers for pre-existing queued changesets
On deployment, changesets that were already in queued have neither verdict_issued_at nor queued_at, since this commit only writes those fields during future verdict issuance and enqueue acknowledgements. Both values therefore remain zero and this timeout condition can never fire, so an existing PR dropped from a merge group stays queued indefinitely and permanently blocks the repository's single queue slot; use a persisted fallback such as the changeset timestamps or backfill these fields.
Useful? React with 👍 / 👎.
| return None | ||
| rt.ledger.update_changeset(changeset["id"], status="superseding", | ||
| detail={**detail, "rebuilt_as": new_id, "superseded_because": why}) | ||
| _outcome(rt, changeset, "superseded", reason=why, replaced_by=new_id) |
There was a problem hiding this comment.
Record superseded only after observing closure
If the close item cannot be delivered or executed, this immediately writes a terminal superseded outcome while the ledger remains superseding and the original PR remains open. These outcome records are permanent gold labels consumed by dataset export, so an outage can incorrectly label an unresolved change as superseded; emit this outcome in the branch that actually observes the PR closed instead.
Useful? React with 👍 / 👎.
Knowledge-service redesign follow-up: knowledge PRs whose signed verdict stopped verifying no longer get stuck.
pauseitem. It is re-signed only after that ack, so the one-queued-PR-per-repo slot is never freed on a timer.enqueueblocks re-signing and holds the queue slot.kb:hold): wait;gate_failed, sent to people once.rebuild of <id>mark, so an interrupted rebuild resumes instead of staging twice.superseding, re-issuing itscloseitem until GitHub shows it closed; it is not counted as overturned by people.rebuild_failed, with the old PR left open.Commit hook approved; full suite and SPEC freshness pass locally.
🤖 Generated with Claude Code