Skip to content

Refine and recheck knowledge changes the gate rejected (design v8) - #228

Merged
tzhouam merged 1 commit into
mainfrom
kb/local-refine
Sep 29, 2026
Merged

tzhouam merged 1 commit into
mainfrom
kb/local-refine

Conversation

@tzhouam

@tzhouam tzhouam commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator

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).
    • The generator gets every reason plus all the original evidence and proposes a refined change on current main, sized for the whole change set.
    • The whole gate runs again. A passing refinement is published; an open PR is superseded.
    • At most 2 rounds. After that the change is refine_exhausted and goes to people with each round's reasons; no more generator calls.
    • The limit survives rebuilds, and recovery finds a staged refinement in any status.
  • Setup refusals (main must allow a direct merge) and external PRs go to people.
  • external._ours recognises the service's PRs in any status.

The commit hook approved it; full suite passes locally.

🤖 Generated with Claude Code

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>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 29, 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-29T05:28:51.661753Z a36d703 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.

@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: 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".

Comment on lines +136 to +137
open_pr = changeset.get("pr_number") is not None
rt.ledger.update_changeset(changeset["id"], status="superseding" if open_pr else "refined",

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 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"]))

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 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 []))

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 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 👍 / 👎.

@tzhouam
tzhouam merged commit 09a771a into main Sep 29, 2026
5 checks passed
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