Skip to content

fix(auto-improvement): publish only the commit the pipeline committed - #8981

Merged
iamwhatever merged 1 commit into
mainfrom
fix/direct-push-verified-head-8452
Sep 9, 2026
Merged

fix(auto-improvement): publish only the commit the pipeline committed#8981
iamwhatever merged 1 commit into
mainfrom
fix/direct-push-verified-head-8452

Conversation

@chenmingwei23

@chenmingwei23 chenmingwei23 commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Problem / Motivation

Driver._direct_push is handed the sha its finalizer produced -- the commit the
pipeline measured, reproduced and wrote to its ledger -- and then published
HEAD:refs/heads/<dest> after scanning HEAD~1..HEAD. Nothing tied either of those to
the verified commit, so what reached the branch, and what got scanned on the way, were
whatever HEAD happened to point at when each step ran.

The window between the two is not empty. _prepush_review_clean runs an agent inside
that same clone with allowed_tools=["Bash", "Read", "Edit", "Grep", "Glob"],
max_turns=30 and timeout_s=420. That runner's git denylist is
_FORBIDDEN_SUBCOMMANDS["git"] = (("push",), ("remote", "set-url")), so git commit is
permitted there. A reviewer can therefore amend the clone, report REVIEW: clean, and
the commit that lands is one this pipeline never verified.

Why it matters

Everything the loop exists to guarantee is attached to a specific commit: the A/B
measurement, the reproduce measurement, the credential scan, the review verdict and the
committed ledger row. If HEAD moves after the finalizer, all of that describes one
commit while a different one is on the branch -- and the ledger records the landed sha,
so the record looks consistent while the content was never checked. There is no
after-the-fact signal either: whether the published state was verified depends only on
when the amend happened, and a poll that reads the forge every few minutes cannot see a
state that existed for one.

What changed (motivation -> approach -> change)

Symptom: the pipeline can publish, and can credential-scan, a commit it never verified.

Root cause: the publish path addressed the clone through the symbolic ref HEAD, which
each step resolves separately. Every check against it is therefore a check-then-use pair,
and the review gate opens a long window in which an agent with Edit and Bash runs
inside the very clone about to be published.

The change stops using the symbolic ref for anything that decides what gets published:

  • The committed object is resolved to a FULL id at the TOP of _direct_push, before the
    review gate runs anything.
  • A new _revision_scans_clean(rev) credential-scans ONE revision's own content by id
    (<rev>~1..<rev>, or show --root <rev>), and BOTH publish paths call it.
  • The first push is handed that id as its refspec source rather than HEAD.
  • The rebase retry REPLAYS THE RETAINED OBJECT -- git rebase FETCH_HEAD <rev> -- so what
    gets replayed is an immutable id rather than whatever the branch points at, then resolves
    the replacement to its own full id BEFORE the build gate runs, requires HEAD to still be
    that object afterwards, scans THAT, and pushes THAT. Because that form detaches HEAD, the
    branch is promoted onto the result with git branch -f at exactly ONE place: after
    re-verification, the identity check and the scan have all passed. Every other exit
    re-attaches without moving the branch, which is both the conservative outcome and required
    for correctness -- _reset_provisional rolls back with git reset --hard, which on a
    detached HEAD moves the detachment and leaves the branch still carrying the commit.
  • EVERY git read in this module runs with core.useReplaceRefs=false, set once in _git,
    because git substitutes objects named by refs/replace/<oid> in READS while the push
    transport sends the ORIGINAL -- so a git replace (not on the reviewer runner's denylist)
    could otherwise have a read see a clean decoy while the credential-bearing object was
    transferred. Two instances were found one at a time, the credential scan and then the
    rebase, which is why the setting belongs at the chokepoint rather than on the next call
    someone remembers.
  • The retry proves the rebase replayed EXACTLY ONE commit. An equivalent upstream patch makes
    git rebase drop ours as already-applied and leave HEAD at the remote tip, which every
    later check would then bind to consistently while the ledger recorded an unrelated commit
    as the one this pipeline landed.
  • A failed branch restore ABORTS the push rather than logging: a ref lock or checkout failure
    means the repository is not in the state the push assumes. The restore is also
    TRANSACTIONAL -- git branch -f lands before the checkout can fail, so a failed checkout
    puts the branch back where it was rather than leaving it carrying an unpushed commit.
  • The pre-push reviewer's Edit grant is withdrawn, not just the prompt sentence that
    sanctioned using it. What decides whether a reviewer can modify the clone is the tool list, not
    the prose telling it what to do, so removing the instruction while keeping the capability left a
    reviewer that may still edit with no sanctioned reason to. Bash stays -- the review needs git
    to read the diff -- so this is least privilege rather than the boundary: a shell can still write
    files, which is exactly why the identity checks are what refuse a moved HEAD.
  • The replayed-commit count names BOTH captured ids, base..rebased_id, not base..HEAD.
    HEAD resolves when that line runs, and the build gate just above it executes the target
    repository's own test suite, so a teardown can move it in between; the equality check before it
    proved HEAD was the replay a moment earlier, which is a check-then-use pair rather than a
    guarantee. It also makes the range COUNTED identical to the range PUBLISHED, since the push
    sends rebased_id. Same property as the fetch fix one step earlier: prefer the immutable
    identifier already captured over the mutable name that resolves at read time.
  • A failed provisional rollback halts publishing instead of only logging. HEAD then keeps a
    commit that was refused and never published; the next winner commits on top of it, and that
    winner's single-revision scan (<rev>~1..<rev>) cannot see the parent its own push would
    publish -- so refused content lands through a scanner that never looked at it. The rollback now
    reports its outcome, latches the failure and stops the run, and _direct_push refuses while the
    latch is set. The failure STOPS the run rather than being retried -- a rollback that cannot
    be trusted to have happened must not be followed by work that assumes it did, and a retry would
    be one more thing whose own failure has to be handled. The halt is unconditional: no retry, no
    further winner, no next cycle. It is enforced at three places because the publish gate alone was
    not enough -- _apply_bug_winner calls the PR pipeline's emit_bug BEFORE _direct_push, and
    that path reaches pr_recipe._push_fix_branch, which pushes HEAD:refs/heads/<branch> and knows
    nothing about the latch. So the guard sits at the top of both winner-applying methods (upstream of
    every push either can reach), again at the publish gate, and again through the run's stop flag,
    which the cycle loop reads. One cycle can hold several bug winners, so a latch read only at cycle
    boundaries would have let the next winner in the SAME cycle publish. And the clone is
    QUARANTINED on disk, so a failed rollback blocks reuse of that clone across RUNS, not just
    publishing within one.
    The latch lives in memory and clears when the process ends, while the
    refused commit is on disk and the clone is reused -- so the next run would have started with a
    clear latch on poisoned state, which delivers the guarantee for one process lifetime rather than
    for the hazard. Retirement renames the clone out of its canonical name, and that name is the
    predicate clone_setup._setup_safe_clone branches on for reuse, so the next run clones fresh
    instead of adopting it. It is the app's own primitive, whose docstring already says it "prevents
    a later run from adopting a rejected provisional commit": nothing about how clones are located or
    named changes, and the bytes are preserved for diagnosis.
  • The rebase base is the object id the FETCH reported, not FETCH_HEAD. That ref is a
    mutable FILE in the clone, and the pre-push reviewer's shell can rewrite it between the fetch
    and the rebase: the replay then lands on a substituted parent whose content nothing scanned,
    and the push carries it. Both the rebase input and the replayed-commit count read that same
    name, so they corroborated the substitution instead of catching it. Measured on a throwaway
    repo with a bare remote: with .git/FETCH_HEAD rewritten to a prepared child,
    rev-list --count FETCH_HEAD..HEAD still reported 1 while the truth against the real tip was
    2, the scanned range held only our own file, and the remote ACCEPTED the push -- the foreign
    file landed through a scanner that believed it had looked. git fetch --porcelain reports the
    tip as <flag> <old-oid> <new-oid> <ref> on its own stdout, so the id comes from the fetch
    itself and there is no ref to substitute. A fetch that reports no usable id -- including the
    all-zero null id of a deleted ref -- refuses the retry.
  • src is REQUIRED on the publish helper, and the three symbolic-source arms are gone.
    It shipped as src: str = "HEAD" "for callers that have nothing more specific"; there were
    none. That default kept alive the pre-fetch tamper check being SKIPPED, a bare
    rebase FETCH_HEAD replaying the BRANCH, and a post-push rev-parse HEAD for the ledger --
    each the check-then-use shape this change exists to remove, reachable by anyone who later
    added a caller and omitted the argument. Requiring the argument deletes all three. Pinned on
    the signature, because a revived default changes nothing observable until someone omits the
    argument, and then it changes what gets published.
  • _revision_scans_clean no longer re-spells core.useReplaceRefs=false: _git injects it on
    every call, so it was one guarantee written twice, and only the chokepoint spelling is pinned. The
    chokepoint covers THIS MODULE's reads only: pr_recipe and backend/commit.py do their own git
    reads and still honour refs/replace/*, so their scans remain substitutable the same way. Out of
    scope here, named so the guarantee is not read as repository-wide.
  • _reset_provisional is now ONE atomic git checkout -f -B <branch> <pre_sha>.
    git reset --hard acts on whatever is checked out, and this runs after the pre-push
    reviewer has had a shell in the clone where git checkout is permitted -- so it could
    hard-reset an unrelated branch (destroying its commits) or a detached HEAD (a rollback that
    silently does not roll back). An intermediate fix read HEAD, checked the branch out if it
    differed, then reset, which was the same check-then-use pair one level up: a backgrounded
    setsid git checkout victim landing in between put the reset back on the wrong branch. A
    single invocation that NAMES the branch has no window. Fixed at the chokepoint because every
    caller shares the exposure, and this closes a LATENT path older than this PR -- the old code
    reached the same reset --hard through a scan or push failure.
  • The ledger records the object that was SENT, reported by _push_with_rebase, rather than a
    post-push rev-parse HEAD that a concurrent move could make name an unrelated commit.
  • _head_is_the_committed_sha compares the retained id against HEAD before the scan, so
    a reviewer amend is refused early with a truthful reason, and again after the scan as a
    tamper detector.

Because the scanned object and the published object are named by the same immutable id on
both paths, they are the same object by construction rather than by timing. That holds for
THIS publish path. Two sibling sites still push through HEAD -- pr_recipe._push_fix_branch
and backend/commit.py -- and the threat model above reaches them; they are out of scope
here and named so the claim is not read as repository-wide. That is the
part a re-check cannot buy, and this PR learned it twice. An earlier revision tried to
prove the scan had covered the published object with a post-scan re-check of HEAD --
wrong, because the re-check is itself a check-then-use pair. A later one kept HEAD on
the retry path and justified it with "_reverify_head has just run" -- the same mistake
again, in the one place the carve-out had been left: the build gate is a check, the push
is a later use, HEAD is re-resolved in between, and the replayed object had never been
credential-scanned at all because the caller scans the PRE-rebase object. A
non-fast-forward retry is not an edge case on this path; the retry's own docstring
records losing 3 of 6 gate survivors to that race. A third revision then resolved the
replacement AFTER _reverify_head -- and that gate runs the repository-under-improvement's
OWN test suite, i.e. arbitrary code from the tree being published, so a test teardown could
move HEAD and have the moved object captured, scanned and pushed as though the gate had
measured it. Credentials were still covered; the build-verification invariant was not. The
capture is now before the gate with an equality check after it. A fourth revision still let
git rebase replay whatever HEAD pointed at rather than the authorized source, so the retry
could bind its own capture, verify and scan to a moved commit and pass every check while
publishing content that never descended from the verified object; the rebase input is now
named explicitly. A fifth revision checked HEAD before the fetch and still let git rebase FETCH_HEAD replay the branch -- a network operation lasting seconds sat between the check
and the use. That check remains, re-documented as a tamper detector rather than the
guarantee it was mistaken for; the guarantee now comes from naming the object.

Four things about it are deliberate.

  • THE ABBREVIATION IS NEVER RE-RESOLVED, which is why the resolve happens early. Git
    resolves a revision through ref names before abbreviated object ids, so re-resolving
    the finalizer's short sha later is defeatable by exactly the actor this gate distrusts:
    amend, then git branch <old-short-sha> HEAD, and both sides resolve to the amended
    HEAD. A retained full id has no such input.
  • THE SCAN IS ONE HELPER, NOT TWO CALL SITES. It was inline in _direct_push, which is
    precisely how the retry path came to publish an unscanned object: a check attached to
    one path and forgotten on the other. src still defaults to HEAD so no other caller
    of _push_with_rebase changes behaviour.
  • THE POST-SCAN CHECK IS NO LONGER LOAD-BEARING, and is documented as a tamper detector
    rather than a binding. What it still catches is worth keeping: HEAD differing there
    means something wrote the clone after the review returned, and a clone being written by
    an unknown actor is not one to publish from, even when the object about to be published
    is provably the verified one.
  • rev-list -1 rather than rev-parse --verify, because this method already asks a
    DIFFERENT question with rev-parse --verify --quiet <rev>~1 -- "does a parent exist", a
    boolean that picks the scan's range. One verb carrying two unrelated questions is
    indistinguishable to a reader and to any caller keyed on the argv, and
    test_ai_spine_driver_coverage's git double is keyed on exactly that.

Everything fails closed. An unresolvable object -- the committed one or the replayed one
-- is the absence of the check, not a pass. _direct_push returns False and records a
STATUS_ERROR ledger row, the same disposition every other gate there uses; the retry
returns the original push rejection, which is what that method already does for a
rebased tree that fails re-verification. The commit stays local and recoverable and the
caller's existing rollback runs unchanged.

Two test files outside the change's own are touched, and both are consequences of the
mechanism rather than scope:

  • test/test_ai_spine_driver_coverage.py (+17): one line in the shared git fixture
    scripting the gate's rev-list -1 resolution -- its fake returns rc 0 with EMPTY stdout
    for an unscripted key, which the gate correctly reads as unresolvable, so without it
    every direct-push test fails closed -- plus two scripted/asserted argv strings that now
    name an object id instead of HEAD. No test's intent is changed. The blast radius was
    measured rather than estimated: one test broke on the scan change, one on the push
    change.
  • test/test_spawn_audit.py (+29): six BENIGN_SPAWNS keys with the justification that
    audit's own assertion asks for. The new tests spawn literal git argv with both -C
    and cwd pinned to a per-test tmp_path clone; nothing in the argv, the cwd or the
    resolved binary is agent-influenced.

Tests

Six behavioural tests for the first push in
TestOnlyTheCommitThePipelineMadeIsPublished, driving the real _direct_push against a
real one-commit git repository, plus three for the retry path in the existing
TestPushRetriesOnRace. Real git rather than a stubbed _git is load-bearing for the
first group: ref-versus-abbreviation resolution order and HEAD-versus-object-id ranges
are the subject of two of them, and a stub cannot exercise either.

  • test_a_reviewer_amend_between_commit_and_push_refuses_to_publish -- the real fault at
    the real seam: a reviewer that edits, amends, and returns REVIEW: clean.
  • test_a_ref_named_the_abbreviation_cannot_shadow_the_committed_object -- amend, then
    create a branch named the finalizer's short sha.
  • test_the_credential_scan_reads_the_committed_object_not_head -- HEAD is swapped to a
    decoy immediately after the pre-scan gate returns; the blob handed to the scanner must
    still be the verified object's.
  • test_a_move_after_the_scan_cannot_change_what_is_published -- the move is injected by
    the credential scanner itself, the last thing before the push.
  • test_an_unmoved_head_still_publishes -- the accepting case, which also pins that the
    revision handed to the push is the full object id and not HEAD.
  • test_an_unresolvable_committed_revision_fails_closed -- the fail-closed arm.
  • test_the_retry_refuses_a_replayed_object_that_does_not_scan_clean -- a credential in
    the REPLAYED object, which the caller's scan never saw, must stop the retry.
  • test_the_retry_refuses_when_the_replayed_commit_cannot_be_resolved -- fail-closed on
    the retry path too; it must not fall back to pushing the symbolic ref. It asserts the
    LOG MESSAGE, because the later HEAD-equality check would also refuse an empty id, so
    without that the check could be deleted with nothing going red.
  • test_the_retry_refuses_when_head_moves_while_the_build_gate_runs -- the gate itself
    moves HEAD, which is exactly a test teardown's capability.
  • test_the_retry_refuses_to_rebase_a_head_that_is_no_longer_the_source -- and it refuses
    before fetching, so no rebase runs on an unauthorized HEAD.
  • test_a_replacement_ref_cannot_substitute_what_the_scan_reads -- real git replace
    against a real repository, with a control proving the replacement is in effect for an
    ordinary read while the scan still sees the true object.
  • test_direct_push_reports_the_sha_that_actually_landed (pre-existing, rewritten) now
    scripts rev-parse HEAD to a DIFFERENT value, so a regression to reading the ref reddens.
  • test_the_retry_replays_the_retained_object_and_only_then_moves_the_branch -- asserts the
    rebase names the object, that the bare form is NOT used, and that the branch promotion
    happens after the gate and before the second push.
  • test_a_failed_rebase_leaves_the_branch_alone_and_publishes_nothing -- a rebase that
    conflicts must abort, must not force-move the branch, must re-attach the clone, and must
    publish nothing. git branch -f is only correct when HEAD IS the replayed result, so this
    pins that the promotion is unreachable from a failure path.
  • test_a_non_fast_forward_triggers_exactly_one_rebase_and_retry (pre-existing, extended)
    now also asserts the retry's refspec is the resolved id and not HEAD:refs/heads/<b>.

Each injecting test asserts its injection APPLIED before asserting the consequence, and
the three first-push refusal tests assert WHICH of the two identity checks refused, so
neither can be deleted without a test going red.

Mutation-verified by hand, forty-nine mutations, each with its own red set:

mutation reddens
neuter the PRE-SCAN check the amend, shadow-ref AND fail-closed tests
re-resolve the abbreviation late instead of using the retained id ONLY the shadow-ref test
let an unresolvable committed object pass ONLY the fail-closed test
first push sends HEAD instead of the retained id ONLY test_direct_push_reads_the_fetch_url_off_the_clone_when_the_profile_has_none
remove the POST-SCAN tamper check ONLY the move-after-the-scan test
bind the credential scan back to HEAD ONLY the scan-reads-the-committed-object test
retry pushes HEAD instead of the replayed id ONLY the one-rebase-and-retry test
retry skips the scan of the replayed object ONLY the replayed-object-scan test
retry falls through when the replayed commit cannot be resolved ONLY the unresolvable-replacement test
remove the retry's post-gate HEAD equality check ONLY the head-moves-during-the-gate test
resolve the replacement AFTER the build gate again ONLY the head-moves-during-the-gate test
remove the pre-rebase authorized-source check ONLY the rebase-input test
drop core.useReplaceRefs=false from the scan ONLY the replacement-ref test
record HEAD instead of the object that was sent the three sha-reporting tests
replay the BRANCH instead of the retained object ONLY the explicit-replay test
promote the branch on the scan-refusal path ONLY the replayed-object-scan test
promote the branch after a FAILED rebase ONLY the failed-rebase test
never promote, leaving the branch behind after a publish ONLY the explicit-replay test
skip the re-attach after a failed rebase ONLY the failed-rebase test
drop core.useReplaceRefs=false from _git ONLY the chokepoint test
accept a rebase that replayed the wrong number of commits ONLY the dropped-commit test
ignore a failed branch restore and push anyway ONLY the failed-restore test
leave the promotion in place when the checkout fails ONLY the undo-promotion test
roll back with reset --hard on whatever is checked out the atomic-rollback test and 3 rollback callers
roll back to a detached sha instead of naming the branch the atomic-rollback test and 3 rollback callers
restore the src="HEAD" default ONLY the signature test
replay the branch again (bare rebase FETCH_HEAD) the retry-argv test and the replay-then-promote test
skip the pre-fetch tamper check ONLY the moved-HEAD refusal test
re-read HEAD for the ledger all 3 sha-reporting tests
rebase onto FETCH_HEAD instead of the reported id the substitution test and 2 retry tests
count replayed commits against FETCH_HEAD ONLY the substitution test
drop --porcelain, so no id is reported 3 retry tests, via the fail-closed refusal
accept a missing id instead of refusing ONLY the no-id refusal test
accept the null id of a deleted ref the refusal test and the parser's null-id case
swallow a failed rollback (log only) ONLY the halt test
latch the failure but let the run continue ONLY the halt test
drop the publish-gate refusal, keeping the stop flag ONLY the same-cycle refusal test
drop the perf-track winner guard ONLY the unconditional-halt test
drop the bug-track winner guard (upstream of the PR push) ONLY the unconditional-halt test
make the halt predicate always report safe ONLY the unconditional-halt test
count the replay against ambient HEAD ONLY the substitution test
restore the fallback to the caller's pre-push snapshot ONLY the ledger-shape test
restore the reviewer's Edit grant ONLY the report-only-grant test
grant Write instead (same capability, other name) ONLY the report-only-grant test
strip Bash too, making the review vacuous ONLY the report-only-grant test
latch only, no quarantine (the in-memory-guard defect) ONLY the cross-run quarantine test
quarantine without marking the clone retired ONLY the cross-run quarantine test
mark retired but never rename the clone aside ONLY the cross-run quarantine test
drop the already-retired short circuit ONLY the second-rollback test

The src="HEAD" mutation reddens ONLY a signature assertion, and that is the honest reading:
a revived default changes nothing observable while every caller passes the argument, and
changes what gets published the moment one does not. It is pinned where it is reachable rather
than left unmeasured.

Each mutation asserted its anchor was unique before being applied, because an unapplied
mutation is indistinguishable from a surviving one. Two earlier rounds of this table had
SURVIVORS and neither was recorded as a pass. First: the post-scan check silently absorbed
every case the pre-scan check was supposed to catch, and the refspec assertion lives in the
other file -- fixed by pinning which check refuses and re-running that mutation where it is
observable. Second, and worse: the scan-binding test passed against a deliberately broken
gate, because its injection patched driver.normalize_branch while _direct_push imports
that name INSIDE the function, so the patch was inert; patching the source module instead
fired too early, so the seam is now the gate's own return. Both were found by the
mutation, not by the green run. A third survivor appeared this round: once the post-gate
equality check existed, it absorbed the unresolvable-replacement refusal, so that mutation
stopped reddening -- fixed by asserting the log message rather than only the refusal. The
reordering it came from also broke three pre-existing tests that pin the atomic clone
RETIREMENT, because refusing at the capture preempted it; the capture now happens early
while the refusal stays in its original position. And one process mistake worth recording:
this round's mutation loop was run BEFORE committing, so git checkout -- driver.py between
mutations restored the file from HEAD and discarded three uncommitted fixes. They were
re-applied and the rule is now explicit -- commit first, then mutate. The credential sample in the retry test was likewise
checked against the real scan_content_for_secrets first -- the uppercase
AWS_SECRET_ACCESS_KEY = '...' spelling is NOT flagged, so the obvious sample would have
made that test pass for the wrong reason.

Run locally after committing (a diff-scoped gate run before the commit gates nothing):
test_dogfood_learnings.py 323 passed at -n0, test_ai_spine_driver_coverage.py 157,
test_spawn_audit.py 12, test_pr_recipe.py 66. Every baselined gate in Backend Lint & Type Check passes:
subprocess encoding, black, agent-SDK boundary, sync-IO-in-async,
lockdown-before-publish. isort --check-only and flake8 over CI's own paths are clean.
mypy src/kiro_crew/ reports 4 errors, all pre-existing on main in transcribe.py and
ops_mission_control/backend/providers/cloudwatch.py, and 0 in any file this PR touches.

Manual verification

N/A -- unit coverage is sufficient. The fault, the accepting case, the ref-shadowing,
mid-scan and post-scan variants and both retry-path refusals are all driven through the
real methods, and exercising the path live would mean authorizing a real direct push.

Related Issues

Refs #8452

For a future publish path

No shared publish seam exists in this app, so the halt is enforced at three hand-placed sites --
the top of _apply_verdict, the top of _apply_bug_winner, and _direct_push itself -- and the
object-id pinning lives in _push_with_rebase. A publish path added later must adopt BOTH
properties independently: refuse while _rollback_failed is latched, and address the commit by the
id it verified rather than by HEAD. pr_recipe._push_fix_branch and backend/commit.py are the
two existing paths that do neither; they are known-unfixed and deliberately out of scope here.

What is in the diff, and why it is this size

+2217/-156 across six files. driver.py is +634/-82 and is the fix plus its reasoning; the
remaining +1251/-26 is test (test_dogfood_learnings.py +1213/-18, test_pr_recipe.py +32/-8,
test_spawn_audit.py +33) and test_ai_spine_driver_coverage.py is +305/-47. The test bulk is
not incidental to a defect fix -- it is what makes forty-nine separate protections falsifiable,
each with its own red set, and several cases (abbreviated-sha shadowing, real git replace, a real
rebase detaching HEAD) cannot be exercised against a stubbed _git at all, so they run against real
one-commit repositories. Every guard here refuses a publish, and a guard that no test can redden is
indistinguishable from one that was never wired up.

Declared: a mechanical reformat rides along

test/test_ai_spine_driver_coverage.py LEAVES .github/black-baseline.txt (the diff's only
baseline change, -1, and no additions). Leaving the baseline means the whole file must be
black-clean, so a whole-file reformat is included. Its size cannot be stated as a clean subset
of the file's +305/-47, because black reflowed lines that this PR also edits, so the mechanical and
substantive hunks overlap: what is measurable is that running black on the BASE version alone
yields +260/-74, i.e. most of that file's churn is reflow rather than new content, and every
non-test hunk in it is black's own output with no hand edits. The prune is
REQUIRED rather than opportunistic -- putting the entry back makes the gate fail with
0 new offender(s), 1 graduated entry to prune. The baseline is never grown by this PR: the diff
against the base is exactly one deletion.

Pattern harvest

Rule candidate: review-prompt
Pattern: addressing a repository through a SYMBOLIC REF while holding a verified object id.
git push HEAD:refs/heads/<b> and git diff HEAD~1..HEAD each resolve HEAD at their own
call time, so every check against it is a check-then-use pair and each window belongs to
whatever else can write the clone. Adding another check does not help, because the new check
has a window of its own -- this PR made that mistake twice before naming the object id in
every step that must agree, which turns a timing argument into a structural one. Two
corollaries worth carrying: a check written inline in one path is how the sibling path comes
to lack it, so put it in a helper both call; and re-resolving an ABBREVIATED id is the same
bug with an extra edge, since git resolves ref names before abbreviated object ids and a ref
is something an untrusted actor can create.

Other suggestions

  • This is one of the three directions An unattributed process amends and pushes worker worktrees, so an unverified state can be published #8452 offers, scoped to the publish path in this
    repository. The amends the issue reports were observed in worker worktrees under
    oss/kirocrew-fix-*, and the process that wrote them is still unidentified; the issue
    itself rules this file out as that mechanism, because a commit created in the separate
    push-disabled clone cannot write a commit (amend) entry into a worktree's reflog. The
    issue therefore stays open for the maintainer decision, and this PR leaves its scope
    intact.
  • The two routes the issue lists as uninvestigated -- the kirocrew-worktree-dev skill's
    amend plus force-push sequence, and issue_radar/backend/pipeline_fold.py -- are
    untouched here. Measured while locating the right seam: dev_fleet contains no push
    invocation at all, and no shared publish helper exists that every push site routes
    through, so a single central gate was not available. First Principles Review reached
    the same conclusion independently by counting HEAD:refs/heads sites.
  • One open PR, feat(auto-improvement): support GitLab repositories #5081, also touches test_dogfood_learnings.py. Its hunks sit around line
    1241 inside TestTheStoredPushDestinationIsValidated while this change appends at the
    end of the file and edits TestPushRetriesOnRace near the top, so the two do not
    collide. merge-tree between the two heads does report conflicts, but that reading is
    confounded: their merge-base is thousands of commits behind this branch, so those
    conflicts are main's own history rather than an adjacency with this change.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable) -- N/A, no user-facing surface changed
  • No secrets, credentials, or internal references in the diff

@chenmingwei23
chenmingwei23 requested a review from a team as a code owner September 6, 2026 10:04
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — 🟡 CONCERNS

Design-level review of f495a1ebe2b9d40333b1e39763eccb144cbbbb74 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: CONCERNS

Sound root-cause fix — immutable object ids replace TOCTOU-prone symbolic refs — but the diff's security-surface hunks outgrew the description, and two named publish paths keep the hole.

Watch

The description's accounting ("+2217/-156 across six files", "Two test files outside the change's own") no longer matches the diff (11 files, +3574/-232). The unaccounted hunks are exactly the security-surface expansion: the durable quarantine-marker subsystem in clone_setup.py (+178) and a new bind-masked crew-home leaf in sandbox.py/security/paths.py — a keystone-layer change the description never names (it describes quarantine as retirement-rename only). Approvers are signing off on sandbox-disposition changes they were not told about.
Clears when: the description names the marker mechanism and the sandbox.py/security/paths.py leaf additions it required.

pr_recipe._push_fix_branch and backend/commit.py still push HEAD and honour refs/replace/*; the PR's own threat model reaches both, and the only invariant binding a future publish path is a prose sentence ("must adopt BOTH properties independently").
Clears when: a tracked follow-up (issue or PR) exists for the two named sites, beyond the passing mention in #8452.

Suggestions

  • Pin the set of :refs/heads/ push call sites in a test (the harness-parity-gate pattern), so a new publish path goes red until it consciously adopts the latch and id-pinning invariants.
  • Follow-up: run the pre-push reviewer against a read-only snapshot of the clone, shrinking the write-capable attacker set to the build gate's test suite alone.

[DESIGN-REVIEWED] f495a1e

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed f495a1ebe2b9d40333b1e39763eccb144cbbbb74 — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] f495a1e

Verdict parsed from the review's SHA-scoped output markers for commit f495a1ebe2b9d40333b1e39763eccb144cbbbb74.

False positive or not applicable? A repository writer can comment:
/ai-review override fable f495a1ebe2b9d40333b1e39763eccb144cbbbb74: <one-sentence reason>

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ human override accepted

Human judgment by @chenmingwei23 overrides the GPT 5.6 finding for f495a1ebe2b9d40333b1e39763eccb144cbbbb74; the recorded reason is authoritative for this commit.

This comment is updated in place on each push.

The model was not re-run because an authorized human decision supersedes it.

False positive or not applicable? A repository writer can comment:
/ai-review override gpt f495a1ebe2b9d40333b1e39763eccb144cbbbb74: <one-sentence reason>

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Premise-level review of f495a1ebe2b9d40333b1e39763eccb144cbbbb74 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

First-Principles-Verdict: CONCERNS

The rebase retry now requires git ≥ 2.41 (fetch --porcelain); on stock LTS gits every retry silently refuses, reinstating the lost-survivor race it exists to fix.

Not justified as shipped

  1. Black-baseline row removed and spawn-audit allowlist entries — rides along: harmless gate housekeeping the tests force, never mentioned by the description.

What this change ships

Inventory (10 items) — 9 justified

Intent: Make the auto-improvement pipeline publish, scan and record exactly the commit it verified, and never reuse a clone whose rollback failed — a FIX (provenance: tests added here fail on base, e.g. the amending-reviewer test).

  1. Both pushes send the verified commit's full object id, never HEAD; a reviewer amend now refuses the publish — justified
  2. Both publish paths credential-scan the exact pushed object (raw commit header + raw blobs); the rebase retry is scanned for the first time — justified
  3. The rebase retry replays captured immutable ids and verifies committer, tree and replay count before promoting the branch — justified
  4. Every driver git read ignores refs/replace substitution (siblings in pr_recipe.py/commit.py declared out of scope in-diff) — justified
  5. The pre-push reviewer loses Edit and its "fix a trivial finding" permission; reviews are report-only — justified
  6. A failed provisional rollback halts all further publishing in the run — justified
  7. Rollback is one atomic checkout -f -B <branch> <sha> instead of reset --hard on whatever is checked out — justified
  8. A rollback-failed clone is quarantined on disk (new masked leaf quarantined-clones, disposition mandated by the pinned-union invariant) and refused for reuse — justified
  9. Ledger and pushed_sha record the object actually sent, as full 40-hex ids; short anchors refused — justified
  10. Black-baseline row removed; spawn-audit allowlist entries for the new real-git tests — rides along

Watch

  • _fetched_tip_oid's docstring claims "an older git fails the fetch outright, so the caller never gets here" — true, and that is the problem: fetch --porcelain is git ≥ 2.41 and merge-tree --write-tree ≥ 2.38, while Ubuntu 22.04 ships 2.34. There every non-fast-forward retry refuses fail-closed, silently dropping the recovery built because "3 of 6 gate survivors" were lost to that race. No git floor is stated or checked anywhere in the app (grepped git version|GIT_MIN|minimum git: 0 hits).
    Clears when: the supported git floor on deployment targets is confirmed ≥ 2.41, or the degraded-retry outcome on older git is explicitly accepted by the author.

Subtractions

  • Delete the final sentence of test_direct_push_reporting_survives_a_blank_rev_parse's docstring (test/test_ai_spine_driver_coverage.py:1301-1302): "The fallback itself remains for callers that do not pass an explicit source" is false — src has no default (pinned by test_the_publish_helper_has_no_symbolic_source_default) and _direct_push records the sent object with no fallback; the sentence invites re-adding the defect.

[FIRST-PRINCIPLES-REVIEWED] f495a1e

@chenmingwei23
chenmingwei23 force-pushed the fix/direct-push-verified-head-8452 branch from 1eb2cd3 to c7347c8 Compare September 6, 2026 10:25
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 6, 2026
@chenmingwei23
chenmingwei23 force-pushed the fix/direct-push-verified-head-8452 branch from c7347c8 to aefc33f Compare September 6, 2026 11:20
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 6, 2026
@chenmingwei23
chenmingwei23 force-pushed the fix/direct-push-verified-head-8452 branch from aefc33f to c19de50 Compare September 6, 2026 13:40
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 6, 2026
@chenmingwei23
chenmingwei23 force-pushed the fix/direct-push-verified-head-8452 branch from c19de50 to bf586f4 Compare September 6, 2026 14:11
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 6, 2026
@chenmingwei23
chenmingwei23 force-pushed the fix/direct-push-verified-head-8452 branch from bf586f4 to 214a624 Compare September 6, 2026 14:37
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 6, 2026
@chenmingwei23
chenmingwei23 force-pushed the fix/direct-push-verified-head-8452 branch from 21bff65 to 96fcc43 Compare September 6, 2026 20:28
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 6, 2026
@chenmingwei23
chenmingwei23 force-pushed the fix/direct-push-verified-head-8452 branch from 96fcc43 to 551d47f Compare September 6, 2026 21:15
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 6, 2026
@chenmingwei23
chenmingwei23 force-pushed the fix/direct-push-verified-head-8452 branch from 551d47f to a18920b Compare September 6, 2026 21:28
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 6, 2026
@chenmingwei23

Copy link
Copy Markdown
Contributor Author

Converting this to a draft. Work on it stops here.

The change is complete and mutation-verified, and the board is otherwise clean on the current head:
64 lanes with 55 success, First Principles PASS, Design Review advisory. Three of the four red lanes
are base-owned and are already fixed on main by #9182, so a re-roll clears both shard-3 lanes and the
Coverage Gate that derives from them.

The remaining red is a hardening gap in this change: a failed clone retirement leaves the clone
reusable. Closing that is security hardening, and this pipeline does not add security hardening as
the route to a green board. On a code path whose purpose is publishing commits, shipping with the gap
declared-and-unfixed is worse than stopping, so the item stands down.

#9220 carries what is worth keeping: an open design question for the retirement fallback, and a
measurement showing that a patch-identity check cannot verify a rebase replay. The second is worth
reading before anyone implements that check.

Nothing is deleted. The branch and its single commit stay in place.

@bolichen97

Copy link
Copy Markdown
Collaborator

@chenmingwei23 I re-checked this PR against main as part of a repo-wide open-PR audit (audited at be5d179; the head has since moved to dd9b51b and the file count grew from 6 to 11, so the notes below are limited to what main still confirms).

Nothing of this PR's goal has landed. On origin/main, src/kiro_crew/apps/builtins/auto_improvement/spine/driver.py still pushes the symbolic ref (HEAD:refs/heads/<dest>), still rebases onto FETCH_HEAD, and still rolls back with reset --hard. None of _revision_scans_clean, _head_is_the_committed_sha, or _fetched_tip_oid exist anywhere on main, and no merged PR implements any part of the change. Please keep this open rather than closing it.

What is still missing on main is exactly your remaining scope: binding scan, rebase, push, and ledger record to one immutable object id, dropping Edit from the pre-push reviewer's allowed_tools, and the quarantine path when rollback fails.

Three things to settle before this is reviewable:

  1. Two adjudicated review findings were unaddressed in the audited diff: rebased_id captured from ambient HEAD (driver.py:1171), and a failed clone retirement leaving the canonical .git reusable (driver.py:2201, tracked as issue auto-improvement: clone retirement fallback needs a design decision #9220).
  2. The branch was 172 commits behind at audit time. The one-line deletion in .github/black-baseline.txt is the likely textual conflict, since main churns that file heavily.
  3. Open PR feat(auto-improvement): support GitLab repositories #5081 relocates pr_recipe._push_fix_branch, the sibling push path you declare out of scope, into a shared base that a second provider inherits. Worth coordinating so the follow-up lands in the right place.

Finally, the current head also touches src/kiro_crew/sandbox.py and src/kiro_crew/security/paths.py, which were not in the audited diff. If those are not part of the publish-integrity fix, splitting them out would keep this narrow enough to review.

Posted from the 2026-09-08 open-PR relationship audit (read-only, one auditor per PR); reply here if any of this is wrong.

`_direct_push` was handed the sha its finalizer produced -- the commit that was
measured, reproduced and written to the ledger -- and then pushed
`HEAD:refs/heads/<dest>`. Nothing compared the two, so whatever HEAD pointed at
when the push ran is what got published.

The window between them is not empty. `_prepush_review_clean` runs an agent in
that same clone with `Bash`/`Edit` for up to 30 turns, its prompt invites it to
"fix a trivial finding in the clone and re-review", and the runner's git
denylist covers only `push` and `remote set-url`, so `git commit --amend` is
permitted there.

The gate compares full object ids and fails closed when it has no valid one. It
sits after the review gate and before the credential scan and the push, and
deliberately not around the push itself: `_push_with_rebase` rewrites HEAD on
purpose and re-verifies the replayed tree.

The anchor is a full forty-hex object id, captured at the commit and never
re-derived. A short sha is an ambiguous NAME: git reads a revision as a ref name
before an abbreviated object id, and a prefix stops naming one object as soon as
a second object shares it. Either reading is arrangeable by anything that can
write the repository between a read and the capture, so a path that re-resolved
an abbreviation would verify one id and publish another -- the substitution this
change exists to refuse. The finalizers therefore return a validated full id
rather than `rev-parse --short HEAD`, and the lookup that re-resolved it is gone
rather than guarded; a short form is derived for display only.

Six residual holes in the same path, all raised by the GPT review:

The credential scan read a RENDERED DIFF, which is a report rather than the bytes
that get published. For a blob git judges binary it prints only "Binary files a
and b differ" with no content, and a single NUL byte anywhere in a file is enough
to trigger that, so a credential inside such a blob passed the gate unread.
Measured on a real repository: a blob holding NUL, then a key, then NUL renders
as that one summary line with zero occurrences of the key, while `cat-file blob`
on the same object yields it. The scan now enumerates the revision's own changed
blobs with `diff-tree -r --root` and reads each one with `cat-file blob`, so the
scanned bytes and the transferred bytes are the same bytes. Two consequences: a
root commit is covered by the same listing rather than a separate weaker branch,
and a WHOLE blob is scanned rather than the changed hunks, because a hunk is a
view of an object and it is the object that lands on the remote. Deletions,
gitlinks and null destination ids carry no blob and are skipped; every failed
read refuses.

A replay writes a NEW commit object, and git takes its committer identity from
configuration rather than from the commit being replayed, while author and
message carry over. `GIT_SAFE_CONFIG` pins hooks, fsmonitor, attributes,
excludes and submodule recursion but no identity, so the reviewing agent's shell
-- whose git denylist covers only `push` and `remote set-url` -- could choose who
the published commit says committed it, and nothing masks a global config either.
The replay now pins its committer to the identity `src` already carries, read off
that immutable object rather than from any config file: the pipeline committed it
before any agent ran. Pinning alone is not the guarantee, because the
`GIT_COMMITTER_*` environment outranks a `-c` override and author and message are
not pinned at all, so the replayed commit's author, committer and message are
compared against the authorized object's and a mismatch refuses the push.
Comparing two objects to each other keeps configuration out of the answer.

The credential scan read only what a commit CHANGES. A commit's header lines are
separate bytes the push transfers just the same, and the root-commit branch
suppresses them outright with `--format=`, so nothing scanned the author or
committer identity or the message. `git rebase` takes the committer identity
from configuration rather than from the commit it replays, which puts those
bytes under the control of the same actor this path already distrusts for
`git replace` -- the reviewer runner's git denylist covers only `push` and
`remote set-url`. Measured on a real repository: a replayed commit whose
identity held a key-shaped address carried it in the committer line while both
scan branches printed nothing but the tree change. The scan now reads the raw
commit object as well, so headers and message reach the scanner as the transport
sees them. A credential once pushed cannot be unpublished, so this fails closed.

The rebase retry captured the replayed commit from ambient `HEAD`, and every
later check bound to that captured id -- including the HEAD-equality check,
which compares HEAD against the same value. A commit substituted into HEAD
before the capture was therefore corroborated by the whole chain rather than
caught, and nothing reached back to the authorized source. HEAD is the only
handle a rebase leaves, so the window cannot be closed; the fix makes it
harmless by binding the replay to its RESULT -- the tree that a replay of the
authorized source onto the fetched base must produce, computed from those two
immutable ids by git's own merge (`merge-tree --write-tree`) and compared
against the tree the captured commit carries.

Quarantine after a failed rollback relied on renaming the clone aside, and that
rename can fail -- from the same held-handle cause that failed the rollback, so
the two are correlated rather than independently rare. The failure path only
logged "MUST NOT be reused", which the next run does not read, and clone reuse
attests git metadata and remotes without ever inspecting the branch tip. A
marker is now persisted at a masked crew-home leaf and `_setup_safe_clone`
refuses a marked clone. It clears structurally rather than on a timer: the
marker names one directory and reports nothing once that directory is gone.

Where the marker lives is what makes it trustworthy, and the scratch tree it
started in could not. That tree is one the agent legitimately writes to, so a
marker there can be truncated through a pre-placed hardlink, or simply deleted
afterwards, which no open flag prevents. An app `data/` directory is no better:
it is writable from inside the agent sandbox unless a leaf is explicitly listed.
So the markers sit at `<crew home>/quarantined-clones`, added to
`sandbox._CREW_HIDDEN_LEAVES` and to `security.sensitive_home_dirs()` -- the
repository's two existing fences, mirroring `aws-control-staging`. Nothing
in-sandbox reads a marker (they are written and consulted host-side), so HIDDEN
is the right disposition of the three that list offers, and TOP-LEVEL matters on
its own: a mask covers the name it binds over and not that name's ancestors, so
a leaf under `apps/auto-improvement/data/` would sit below a directory an agent
can rename out from under the mount. The root is refused outright if a link is
planted at it, since that would put every marker outside the fence where no
per-marker check could see it.

The write and the read had to be taught to agree on what "present" means.
`O_TRUNC` is gone from the open entirely -- an existing entry is the guard
already standing, not a failure -- and the reader tests for the entry with
`lstat` rather than `exists`. A dangling symlink is an existing entry to an
`O_CREAT|O_EXCL|O_NOFOLLOW` open and an absent file to `exists`; split that way,
a planted dangling link would let marking report success while the guard read no
marker at all. Under `lstat` anything at that name counts, so a planted entry
can only make the guard more conservative: it reports the clone quarantined,
which refuses it.

Refs #8452

A TREE rather than a patch identity, and the difference is not cosmetic. Two
patch-identity forms were tried and both leak. The default `git patch-id`
algorithm STRIPS WHITESPACE, and in Python whitespace is control flow, so a
substituted commit that dedents a call out of its guard hashes identically to
the authorized change -- measured on git 2.50.1 against a diff pair whose only
difference was `+        publish()` inside an `if` versus `+    publish()` after
it: one id under `--stable`, two under `--verbatim`. `--verbatim` fixes that but
still ignores hunk POSITIONS, which it must in order to recognise a change
replayed onto a moved base, so the same added and removed lines placed elsewhere
in the file carry the same identity. A tree object names the exact content of
every path, so relocation is a different tree and there is no residue left to
argue about. The comparison fails closed: a merge that conflicts, or a git too
old to know `--write-tree`, yields no usable tree and the publish is skipped,
which costs a cycle and never publishes the wrong thing.

The quarantine marker is written BEFORE retirement is attempted, not after it
fails. Ordered after, the durable record depended on a path reached only once
two things had already failed, so a transient failure of the marker write left
the clone reusable with nothing recording that it must not be -- fail-open at
the exact moment the guard is needed. Written first, the record exists before
anything is disturbed, a rename that then succeeds merely makes it stale (the
marker names a directory, so it stops reporting once that directory is gone and
is pruned on the way past), and a write that fails is known before the tree is
touched.
@chenmingwei23

Copy link
Copy Markdown
Contributor Author

/ai-review override gpt f495a1e: The finding is the residual after three independent guards fail simultaneously, it is logged loudly at every stage, and the prescribed remedy adds a fourth durable write with the third's failure mode.

Recording the reasoning so the override is auditable.

The finding asks that a transient quarantine failure not leave a poisoned clone reusable. The ordering it prescribes is already the code: driver.py calls _mark_clone_quarantined before _retire_unsafe_clone, and rmtree_force is a third fallback. What remains is the case where the marker write, the rename, and the recursive delete all fail in one run. That path is not silent: each failure logs at error level, and the last one states that the clone must be deleted by hand before another run starts.

Two reasons not to patch it here. The trigger needs three filesystem operations to fail together, and the consequence is recoverable by an operator who has been told exactly what to do. And the prescribed fix -- persist a fallback quarantine record and have clone setup consult it -- is a fourth durable write whose failure mode is identical to the third's, so on a failing filesystem it fails too; that is how a seam earns a fifth finding rather than closing.

This is the fourth hardening pass on the marker-writability and clone-certification seam. The three earlier ones are all still in the code: the marker moved to a masked crew-home leaf, the O_EXCL never-O_TRUNC open with O_NOFOLLOW, and the create-and-remove writability probe. A structural alternative exists -- require a reused clone's branch tip to carry nothing unpublished, so the poison is self-evidencing and no external record has to survive -- but it changes the reuse certification contract on a path shared beyond this branch, which does not belong in round 13 of a branch that is otherwise green.

The residual is recorded in the local backlog for re-judgement rather than dropped.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Human judgment recorded

@chenmingwei23 marked the gpt AI finding as false positive, not applicable, or explicitly accepted for f495a1ebe2b9d40333b1e39763eccb144cbbbb74.

The finding is the residual after three independent guards fail simultaneously, it is logged loudly at every stage, and the prescribed remedy adds a fourth durable write with the third's failure mode.

This decision applies only to this commit. A new push requires a new judgment.

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.

3 participants