Files
fusion/packages/engine/src/__tests__/git-identity.test.ts
gsxdsm 1da6375c68 fix(git): Fusion sets its own commit identity, per agent
Auto-merge did nothing on a fresh container: tasks reached in-review, the merge
began, and FN-001 sat at status:merging forever with no error in the UI. The
cause was that git could not commit at all — `git commit` in the container dies
with "Author identity unknown ... Please tell me who you are", because a
container has no git identity and Fusion was borrowing the environment's.

Only workspace-fence-ref.ts ever passed an explicit identity. The merge commits,
the merger's --amend, and the experiment git-ops all inherited whatever
user.name/user.email the host happened to have. The existing commitAuthor*
settings only added a Co-authored-by TRAILER; they never set the author.

resolveCommitIdentity (packages/engine/src/git-identity.ts) now resolves:
operator commitAuthor* settings > the acting agent > Fusion. An agent-derived
identity is `<Agent Name> (Fusion) <slug@agents.fusion.local>`, so history
attributes a change to the agent that made it instead of one anonymous bot, per
the operator's request.

Applied at mergerCommitEnv — the single env all eight merger commit sites share
— and via `-c user.name/-c user.email` for merger-ai and experiment git-ops,
which build their own argv and bypass that env. Author AND committer are pinned:
git fails on a missing committer just as hard as on a missing author.

`commitAuthorEnabled: false` returns undefined and restores ambient git config,
so an operator who wants commits authored as themselves keeps that.

Verified: 9 new helper tests, 95 engine auth+identity tests, pnpm test:gate green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 23:49:33 -07:00

68 lines
2.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
commitIdentityArgs,
commitIdentityEnv,
FUSION_FALLBACK_IDENTITY,
resolveCommitIdentity,
} from "../git-identity.js";
/*
FNXC:GitIdentity 2026-08-18-07:55:
Fusion-authored commits must carry an identity Fusion chose. Relying on ambient `user.name`/
`user.email` meant that on a host with none — container, CI, fresh machine — git refused with
"Author identity unknown" and an auto-merge stalled at `status:merging` with nothing surfaced.
*/
describe("resolveCommitIdentity", () => {
it("attributes a commit to the acting agent", () => {
const identity = resolveCommitIdentity({ agent: { name: "QA Engineer", id: "agent_1" } });
expect(identity).toEqual({ name: "QA Engineer (Fusion)", email: "qa-engineer@agents.fusion.local" });
});
it("falls back to the agent id when it has no name", () => {
expect(resolveCommitIdentity({ agent: { id: "agent_42" } })?.email).toBe("agent-42@agents.fusion.local");
});
it("prefers explicit operator settings over the agent", () => {
const identity = resolveCommitIdentity({
agent: { name: "QA Engineer" },
settings: { commitAuthorName: "Release Bot", commitAuthorEmail: "release@example.com" },
});
expect(identity).toEqual({ name: "Release Bot", email: "release@example.com" });
});
it("still yields an identity when no agent is known", () => {
// The important half: never return undefined just because the caller lacks agent context.
expect(resolveCommitIdentity()).toEqual(FUSION_FALLBACK_IDENTITY);
});
it("opts out entirely when the operator disables Fusion authorship", () => {
// undefined means "leave git alone" — the escape hatch for commits authored as the operator.
expect(resolveCommitIdentity({ settings: { commitAuthorEnabled: false }, agent: { name: "QA" } })).toBeUndefined();
});
it("does not emit an unusable email for a name with no alphanumerics", () => {
expect(resolveCommitIdentity({ agent: { name: "***" } })).toEqual(FUSION_FALLBACK_IDENTITY);
});
});
describe("commit identity plumbing", () => {
it("pins committer as well as author", () => {
// Author alone is not enough: git fails on a missing COMMITTER identity just as hard.
expect(commitIdentityEnv({ name: "N", email: "e@x" })).toEqual({
GIT_AUTHOR_NAME: "N",
GIT_AUTHOR_EMAIL: "e@x",
GIT_COMMITTER_NAME: "N",
GIT_COMMITTER_EMAIL: "e@x",
});
});
it("emits nothing when authorship is opted out", () => {
expect(commitIdentityEnv(undefined)).toEqual({});
expect(commitIdentityArgs(undefined)).toEqual([]);
});
it("builds -c overrides for argv callers", () => {
expect(commitIdentityArgs({ name: "N", email: "e@x" })).toEqual(["-c", "user.name=N", "-c", "user.email=e@x"]);
});
});