Skip to content

D1063 — An identity refusal is its own error, and unpushed commits are re-authored by an offline operator command

Decision. A push the remote refuses because of the commits' author or committer is reported as an identity refusal (*kb.ErrIdentityRejected), tested before the protected-branch wording, and never tells the operator to unprotect a branch. Commits that exist only in the local clone are re-authored, under the configured git.identity, by the offline command cartographer kb reauthor <kb> [--apply], which runs under the KB's process lock, moves the branch with a compare-and-swap update-ref, and refuses anything already reachable from a remote ref. Messages are kept, so the Cartographer-Author, -Token and -Host trailers stay as the record of who really wrote.

Why. The generic pre-receive hook declined line accompanies both a branch protection and a membership rule, so the old classification sent operators to fix the wrong thing. The automatic realign (D950) deliberately leaves a token-authored commit alone, and such a commit can never publish under a membership rule: nothing repaired it, and the only way out was git by hand in the clone, which agents are forbidden to run. The cost is a history rewrite, accepted only on commits no one else can have seen, behind a preview.

Alternatives rejected. - A flag on kb repair: that command is a thin MCP client for mechanical lint repairs, and has neither the server config nor the clone. - A new MCP tool: a history rewrite is not a write (a handler never locks, commits or syncs), the identity is a server-file setting, and any rw token would gain the power to rewrite history. - Making the automatic realign re-author token-authored commits too: a token id names the real writer, and silently replacing it on every sync loses that without an operator deciding to. - Stopping the service for the rewrite: the process lock and the compare-and-swap make a race fail safely. - An audit-trail entry: no offline command writes one; the reflog, stderr and this file record it.

Consequences. last_error text is the contract (no new field in sync_status). RealignCommitters keeps its decisions byte for byte and shares its commit loop with ReauthorCommits. A range holding a merge is refused by both; the operator resolves that on the remote side. Any new push-refusal wording belongs in gitx.IsIdentityRejection, and the identity test must stay ahead of the protected one.