Skip to content

fix(node-sync): write the git identity through the merged view, not the overlay upper#546

Merged
gbasin merged 2 commits into
masterfrom
fix/identity-merged-write
Jul 16, 2026
Merged

fix(node-sync): write the git identity through the merged view, not the overlay upper#546
gbasin merged 2 commits into
masterfrom
fix/identity-merged-write

Conversation

@gbasin

@gbasin gbasin commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

#541 shipped broken and prod proved it

A live prod session with a correctly resolved identity still committed as Centaur AI <ai@centaur.local>:

identity file written : 19:06:34.5   (upper: present, 146 bytes)
agent's commit        : 19:06:44     AUTHOR: Centaur AI <ai@centaur.local>

Not a timing problem — the file was there 10 seconds early. It was in the wrong place.

UPPER  : /var/lib/centaur/overlays/asbx-…/.config/git/atrium-identity   ← EXISTS
MERGED : /run/centaur/merged/asbx-…/agent/.config/git/                  ← No such file or directory
mount  : upperdir=/var/lib/centaur/overlays/asbx-…

The daemon wrote into the overlay's upper before mount_overlay. Fine for a cold create; silently useless for a warm claim, which is the common case — a warm pod's overlay is mounted at pod creation, minutes before the claim that gives it an identity. Modifying an upperdir behind a live mount is undefined: the file lands on disk while the merged view keeps serving its cached negative dentry. It also leaves the path incoherent — a later mkdir through the mount failed with ESTALE.

The lowerdir comment a few lines above in the same loop describes this exact hazard ("the mount keeps the old dir inode pinned... re-materialized files never become visible"). The identity write walked straight into it.

The fix

Write through mounted.merged after the mount — the supported way to modify an overlay.

More importantly: materialize_git_identity now takes the OverlayMountPlan and derives merged itself, instead of accepting a bare &Path. That's the real fix. The old signature is why the wrong call site type-checked; deriving the path makes passing the upper unrepresentable rather than merely discouraged. It immediately caught its own call sites at compile time when I changed it.

Why the tests missed it

Every unit test used a plain tempdir, where upper and merged are the same directory — so they passed either way and proved nothing about the thing that broke. Adds tests/git_identity_overlay_visibility.rs: a real overlay, mounted first, then the identity — the warm-claim shape — asserting through the merged view, plus that a 204 clears it there too. Runs in the privileged Linux suite (skips silently without root).

Known limitation (documented at the call site)

A warm pod's ready marker predates the claim, so there's no write-before-ready guarantee here — an agent could commit in the seconds before the identity lands. That degrades to the baked identity rather than a wrong one, which is why it's acceptable for now; closing it properly needs the claim path itself to carry the identity.

Verification

Linux clippy -D warnings, cargo fmt, 257 lib + 16 contract + privileged overlay tests, all green in the Docker gate (host clippy is blind to linux_daemon).

gbasin added 2 commits July 16, 2026 15:18
…he overlay upper

#541 shipped git author identity and it did not work in production. A live prod
session with a correctly resolved identity still committed as
`Centaur AI <ai@centaur.local>`.

The daemon wrote the identity into the overlay's `upper` before `mount_overlay`.
That is fine for a cold create, and silently useless for a warm claim — which is
the common case. A warm pod's overlay is mounted when the pod is created, minutes
before the claim that gives it an identity, and modifying an upperdir behind a live
mount is undefined: the file lands on disk (the upper listed it, 10s before the
commit) while the merged view keeps serving its cached negative dentry, so the
agent never sees it. It also leaves the path incoherent — a later mkdir through the
mount failed with ESTALE. The lowerdir comment a few lines up in the same loop
describes this exact hazard; the identity write walked into it anyway.

Write through `mounted.merged` after the mount instead, which is the supported way
to modify an overlay.

`materialize_git_identity` now takes the `OverlayMountPlan` rather than a bare
`&Path` and derives `merged` itself, so passing the upper is no longer expressible.
That is the actual fix: the old signature made the wrong call site type-check, and
every tempdir unit test passed while production was broken.

Adds tests/git_identity_overlay_visibility.rs — a real overlay, mounted first, then
the identity, asserting through the merged view. That shape is the only one that can
catch this; a tempdir test passes either way.

Ordering caveat, documented at the call site: a warm pod's ready marker predates the
claim, so there is no write-before-ready guarantee here — the agent could commit in
the seconds before the identity lands. That degrades to the baked identity rather
than a wrong one. Closing it for real needs the claim path to carry the identity.
…lly gate

cargo test runs as the unprivileged runner, so every test needing a real overlay
mount self-skips there. That is exactly how the identity bug reached prod green:
the write went to an upperdir behind a live mount, and no lane could see it.
@gbasin
gbasin added this pull request to the merge queue Jul 16, 2026
Merged via the queue into master with commit b5185a0 Jul 16, 2026
16 checks passed
@gbasin
gbasin deleted the fix/identity-merged-write branch July 16, 2026 20:10
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.

1 participant