Skip to content

D950 — The writer commits: author and committer are the same identity, and the server file names it per KB

Decision. A commit Cartographer makes for a known writer has that writer as author and committer. The instance that recorded a write moves to a Cartographer-Host: <host> trailer, appended before Cartographer-Token. The server file gets two keys, git.identity and git.identities (a map by KB name, with default), and the identity they give a KB is its default: the author and committer of every commit with no writer of its own (the unattended repair, the external-changes commit, a sync merge, conflict resolution) and of a write that sends no valid Cartographer-Author. The server identity Cartographer <cartographer@<host>> stays the last fallback, for a KB with no identity. A sync that rebases local commits gives each one its author back as committer, and a commit authored by exactly the server identity (made before the identity was configured) is re-authored and re-committed as the KB's configured identity. git.author_name/git.author_email, the 0.24 keys, are moved to git.identity by the server's realignment.

Why. D904 made the committer the server, on the premise that it "needs no account on the forge". A forge with a membership push rule (GitLab member_check, "the commit author must be a member") checks the committer as well as the author: measured on eight KBs whose remote is such a GitLab, every push of a 0.25.0 commit was refused, first for the author, then — after the author was fixed by hand — for the committer. The author could be a real person (the header), but the committer never could, and neither could the author of what has no client behind it (the repair, the merges) or of a client that cannot send the header (Kiro's MCP config has no headers). So 0.25.0 could not publish to such a remote at all. The identity is the server's setting, per KB, because it is the machine's: a KB's settings.yaml is committed and shared by every person who writes to the KB, and would put one person's identity on everyone's commits.

The cost: an author is a claim, not a proof, and now the committer is that claim too, and a write without a header is attributed to the configured identity whichever token made it. What the server vouches for is unchanged: the Cartographer-Token trailer (unforgeable, last) names the token that wrote, and Cartographer-Host the instance. concept_history reads both from trailers, never from the author line.

Alternatives rejected. - The client names the identity per KB and registers it with the server (identities in .cartographer.yaml, posted by sync to a /api/writer route): it follows the person, but the server then keeps state any whole-KB writer can overwrite for the others, a new route to protect, and a registration that is lost when the client does not sync; and the maintainer's rule is that a KB's identity is a server setting. The client's identities still decides the header it sends, and the header still wins. - The identity in the KB's settings.yaml (D906): committed, so shared by all writers of the KB. - Keep the server as committer and ask the forge to relax the rule: the rule is a group policy that belongs to the forge's owner, and "turn off the check" is not an answer a product can give. - The committer taken from the process or git config of the machine: it is what 0.24 did and D904 removed it, because it made history depend on whichever git config the service happened to run under. - kbs[].identity: kbs[] has no entry for a KB discovered under data, which is most of them, and an entry with no remote is ignored by design; a map by name reaches every KB.

Consequences. Breaking for anyone who matched on the committer being Cartographer <cartographer@host>: it is the writer now; the host is in the trailer. A KB with no configured identity behaves as 0.25.0 until the operator sets one. A server shared by several people gives every header-less write of a KB the same configured identity: the token trailer is what tells them apart. A forge rule that checks the committer's identity ("reject unverified users") no longer stops a header that claims someone else, since the committer is now that claim too: the signed audit log, which ties every write to its token and commit SHA, is the attribution to trust, the author and committer lines are not. The realign rewrites only a linear range above the upstream, never a published commit, and returns ErrNotLinear, logged, for a range with a merge or a root commit; it never re-authors a commit authored <token id> <cartographer@host>, which names the token that wrote it.

A future change here must preserve all of these: - the committer of a commit with a known writer is that writer, whatever the server's environment says; - the configured identity is attribution, never authorization; - Cartographer-Token stays the last trailer and Cartographer-Host the one before it; a caller's copy of either is dropped; - the realign never touches a commit that is already on the upstream, and never takes over a token's authorship.

Amends D904 (the committer is no longer always the server), D265 (the committer fallback now has a writer to fall back to), D46 and D906 (a server-side identity returns, as one key and one map).