Kernel: a dependency update needs no model review
Closes the fourth gap of the epic "Epic: nine gaps in the Kernel, found in review": a pull request of class dependency with green checks waited for a model review, and one was admitted.
Stacks on "Kernel: an unknown merge state defers" (#68), which stacks on "Kernel: a Pause stops approvals and merges, not only Runs" (#67). It will be rebased onto main once they merge.
What changes
-
kernel/derive.py: one function,required_model_lenses(facts, config), says which model Lenses an item needs, and the Kernel uses it where it waits for reviews. A dependency update needs none. -
A dependency update is a pull request of a class named in
required_lenses.model_not_required_forwhose head was pushed by the identity that opened it. The class is worked out from who opened it, and the last pusher is GitHub's record of the push, not a commit's free-text author. Anyone else's push, including the author Role's fix after a failed check, puts their content in it, and it needspr-reviewagain. No new fact is needed. -
Following the architect's ruling on the issue, a Finding a model Lens has already raised gates whatever the class:
gating_threadsreads every configured model Lens. A bot that recreates its branch after someone else's push therefore cannot drop a threadpr-reviewopened, a security Finding among them. -
config/defaults.jsonandschemas/config.schema.json: the new key, as data (§4.2). The schema allows onlydependency;examples/invalid/config.a-change-by-a-person-skips-the-model-review.jsonlistsmaintainerand is rejected. -
required_lenses.deterministicnow needs at least one entry, with the counter-exampleexamples/invalid/config.no-deterministic-lens.json. This story makes an empty list dangerous: it would approve a dependency update with no check at all. This is criterion 3 of "Kernel: two smaller gaps in what it is given" (#39), and it lands here. -
tests/test_decide.py:test_a_dependency_update_needs_no_model_reviewloses@pending, leavesNotYetHeldand asserts both sides. Seven tests join it:- every other class needs a model review;
- a push by someone else needs one too;
- each deterministic Lens failing sends the update back, and pending makes it wait;
secret-scanandcode-scanningjudge security; - a
neutraldeterministic check does not pass it; - a writer's thread still gates, and a stranger's does not;
- a model Lens's thread gates, including after the bot recreates the branch, with the content changed or unchanged;
- the order: a Hold, an unknown merge state, a conflict, a Pause and a standing approval still bind.
-
tools/mutants.py: seven mutants.- "a dependency update waits for a model review"
- "a model Lens's thread does not gate a dependency update"
- "a dependency bot's pull request someone else pushed to skips the model review"
- "a class the configuration does not name skips the model review"
- "a dependency update converges before its checks are read"
- "a dependency update converges before its threads are read"
- "a neutral deterministic check passes"
33 of 33 killed.
-
Documents:
- Decision 0012 loses "not held yet" and gains rows for each boundary.
- One of those rows, waiting on
adapters/, sayslast_pushermust be the identity GitHub records as pushing the head, never a commit's author or committer. It also says classdependencyis given only for an exact App identity, and a fork is alwaysoutside. - The design's §3.3 says what
last_pushermeans. §4.2, the dependency-update row of §4.6, the glossary's Class entry andconfig/AGENTS.mdsay how a dependency update is recognised.
Acceptance criteria (from "Kernel: a dependency update needs no model review")
-
test_a_dependency_update_needs_no_model_reviewpasses with@pendingremoved. - One function says which model Lenses an item's class requires, and is used where the Kernel waits for reviews. A Finding a model Lens has raised gates whatever the class. Each has a test and a mutant.
- A person's thread still gates a dependency update.
- No document says
not held yetbeside this test.
Left open, on purpose
- Code the bot writes itself (Renovate post-upgrade tasks, vendored code) is still a dependency update. Catching it needs the
changed_pathsfact of §3.3 and a set of manifest paths in the configuration. - That a bot never becomes the last pusher over someone else's surviving commits is bot behaviour, to settle by trial (§6).
- A same-named check from another App, or a re-run, can hide a failure, because the last entry wins. That belongs to "Kernel: a Lens is read only from the App that reports it" (#60).
_wanted_runreadsmodel[0], which is safe today with one model Lens.- What a
neutralconclusion means is not changed here; it is pinned only as not passing a dependency update. That is "What each check conclusion means" (#35). - The simulator's world is always of class
maintainer, so the tests carry this rule.sim/world.pyis shared with other stories in the epic.
Checks and reviews
make verifyandpython3 -m tools.mutantspass on agit archivecopy, which avoids the nested worktrees of "Verify passes in a checkout that holds other worktrees" (#64).- The new tests were written before the code. The ones that depend on the rule failed first: needing no model review, a writer's thread, the order, and a model Lens's thread gating after the ruling. The others passed throughout, because they pin existing behaviour: the other classes, someone else's push, each deterministic Lens, and
neutral. Theneutralmutant survived every test until its test was added. - The second model family's read is the Gemini code review and the other bot reviewers on this pull request, as the Maintainer directed. Not merged or enqueued; the Maintainer merges.
Closes #32
🤖 Generated with Claude Code