Repository navigation
Refine and recheck knowledge changes the gate rejected (design v8) - #228
Conversation
A change the quality gate rejects is never merged. The service's own changes
are now refined with the rejection reasons and judged again, instead of going
straight to people.
- refine: a change set of kind intake, rebuild or refine is refined in two
cases: it failed its gate when staged (status failed with a fail decision),
or the publisher's local gate refused it because of the change itself (new
status refine_needed, set by the merge ack).
- Under the scheduler's lease, and only while the repository is not
paused, the generator gets every reason with the original operations:
local-gate problems, L1 issues, per-dimension L2 reasons, consistency
conflicts and decision reasons.
- It proposes a refined change on current main (kind refine, with
refine_round and refine_history), and the WHOLE gate runs again.
- The original becomes refined, or superseding (its PR closed) when a PR
was already open, like a rebuild. A passing refinement is published.
- At most 2 refinements. After that the change is refine_exhausted: one
human-queue entry lists every round's reasons, and no further generator
calls are made.
- A refinement is found again by its evidence mark in any status
(companion_pending included; new Ledger.changesets_of_kind), so a crash
never stages it twice.
- The generator gets ALL the original evidence, since an intake change
set can combine several events.
- A rebuild carries the refine lineage (refine_round/refine_history), so
the limit never resets because context moved.
- A refusal caused by how the repository is set up (main must allow a direct
merge) goes to people: refining cannot fix it and would only loop. External
PRs refused by the local gate stay gate_failed for people; their daily
recheck is the next change.
- external._ours now finds the service's own PRs in ANY status (new
Ledger.changesets_for_pr), so a PR in refine_needed or refine_exhausted is
never picked up again as someone else's PR.
- draft_changes takes max_operations: a refinement covers the whole change
set, up to 6 operations per combined event, and at least as many as the
original change had.
- gate_and_stage accepts extra_detail.
Tests: a rejected intake is refined with the reasons and passes; a change
still rejected after two refinements stays unmerged for people with no
further generator calls; a PR the local gate refused is replaced by a refined
one; a refinement is never staged twice; an external refusal and a setup
refusal go to people; all the evidence reaches the refinement; a rebuild
keeps the refine limit; a refinement staged before a crash is found in any
status; our own PRs are never judged as external in any status; a
refinement can carry 7+ operations.
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: a36d703042
ℹ️ 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".
| open_pr = changeset.get("pr_number") is not None | ||
| rt.ledger.update_changeset(changeset["id"], status="superseding" if open_pr else "refined", |
There was a problem hiding this comment.
Preserve the original PR when refining a failed rebuild
When a context-change refusal causes rebuild() to stage a rebuild that itself fails the gate, the failed rebuild has no pr_number even though its parent does. advance() then refines that PR-less changeset, so this condition is false: a passing refinement is published without closing the original PR, while the original is left in rebuild_failed outside IN_FLIGHT. This leaves the rejected PR open and unmonitored alongside its replacement, allowing a human to act on the stale PR; retain or resolve the parent PR lineage before deciding whether to supersede it.
Useful? React with 👍 / 👎.
| "superseded_because": "refined after the quality gate rejected it"}) | ||
| _outcome(rt, changeset, "refined", replaced_by=new_id, round=round_) | ||
| if open_pr: | ||
| _request_close(rt, lifecycle.repo, rt.ledger.changeset(changeset["id"])) |
There was a problem hiding this comment.
Pass the refined replacement ID to the close request
For a local-gate rejection of an existing service PR, this closes the old PR through _request_close, but that helper formats its comment from detail["rebuilt_as"]. Refinement records only refined_as, so every such PR is closed with a misleading comment like Superseded by change set None, rebuilt on current main; update the helper or pass the actual refined changeset ID.
Useful? React with 👍 / 👎.
| with trace_context(draft_key=key, _accepted=holder, step="refine", refine_of=changeset["id"]): | ||
| # the whole change set is refined at once: as many operations as | ||
| # the events it combines may carry | ||
| limit = max(MAX_OPS_PER_EVENT * max(1, len(evidence)), len(detail.get("operations") or [])) |
There was a problem hiding this comment.
Count source events rather than refinement markers
The evidence list is not equivalent to the original event count: each successful refinement appends a synthetic REFINE_MARK, and rebuilds append a REBUILD_MARK. Because those markers are retained in later rounds, a one-event change may request up to 12 operations on its second refinement instead of the documented six-per-event limit, and rebuilds inflate it further. Derive the multiplier from original source evidence only, while retaining the previous-operation floor separately.
Useful? React with 👍 / 👎.
PR 2 of the v8 knowledge-service change: a change the quality gate rejects is never merged. The service's own changes are refined with the rejection reasons and judged again.
refine: covers intake, rebuild and refine change sets that failed their gate when staged, or that the publisher's local gate refused because of the change itself (refine_needed).main, sized for the whole change set.refine_exhaustedand goes to people with each round's reasons; no more generator calls.mainmust allow a direct merge) and external PRs go to people.external._oursrecognises the service's PRs in any status.The commit hook approved it; full suite passes locally.
🤖 Generated with Claude Code