Skip to content

tcfs: loser-side no-loss guard for git keep-both (TIN-2552)#534

Merged
Jesssullivan merged 12 commits into
mainfrom
feat/keep-both-pr4-loser-guard
Jul 6, 2026
Merged

tcfs: loser-side no-loss guard for git keep-both (TIN-2552)#534
Jesssullivan merged 12 commits into
mainfrom
feat/keep-both-pr4-loser-guard

Conversation

@Jesssullivan

@Jesssullivan Jesssullivan commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the PR-4 loser-side no-loss guard for divergent raw .git keep-both convergence.

  • before a ref-class pull overwrites a local branch head whose SHA is not reachable from the incoming SHA, snapshot the repo to the machine-local state dir and park the local SHA under refs/tcfs/theirs/<self-device>/**
  • before a ref-class local delete, park top-level branch/stash refs and defer packed-refs, tags, module refs, detached HEAD, and unresolved ref targets
  • defer remote ref-class deletes because TCFS cannot locally park a remote-only .git ref table
  • defer instead of overwriting when the guard cannot prove safety, the incoming commit object is absent, bundle/parking fails, or the target is non-parkable
  • treat top-level and submodule packed-refs as opaque: byte differences defer rather than rewriting packed-only refs
  • preserve repeated divergence by suffixing occupied park refs with -<sha12> and widening the SHA prefix if that suffix is also occupied
  • add the G5-git-13 harness row and focused coverage for branch parking, ref deletes, non-head deferral, packed-refs deferral, submodule deferral, exact .git path parsing, and suffix collision handling

Tracker

Linear: TIN-2552
Parent: TIN-1620

Validation

  • cargo fmt --all
  • git diff --check

No local cargo/nix build was run on neo. This PR is intended to validate through GitHub Actions / remote builders only.

Notes

This is the final keep-both rung from the design doc: it closes the crash/order window where the non-resolving device could pull the winner's refs/heads/* before its own old head was reachable through refs/tcfs/theirs/**.

Update — 2026-07-05 docs correction

Latest head 6f1ae21 adds a docs-only correction: PR-4 is not framed as blocked on PZM hardware. PZM SSD/RWX re-enumerated and verified clean; builder transport hardening (ssh-ng:// Determinate Nix vs ssh:///serve-style or GF/remote-cache execution) remains tracked separately under TIN-1620/#524. Code remains the 2e05c8b safety head.

Update — 2026-07-05 CAS fix round

Latest head e5398c8 closes the fresh adversarial P1/P2 findings:

  • Ref pulls no longer fall through to raw file overwrite for classified git refs. They download/parse the incoming ref blob, prove the incoming commit object exists even for new local refs, then apply git update-ref <ref> <incoming> <expected-old> (or zero-OID for a new ref). A live ref move between proof and mutation now defers/replans instead of overwriting.
  • Ref local deletes no longer use raw remove_file after parking. They use git update-ref -d <ref> <expected-old> and defer on mismatch.
  • Added regressions for missing-object new refs and CAS rejection for moved update/delete refs.
  • Local validation on neo remains hygiene-only: cargo fmt --all -- --check and git diff --check; no local build/test.

Fix round: ref-class raw fallthrough closure (4a46851)

Confucius adversarial review found three initialized-gitdir paths that could still fall through to raw writes. This head closes them:

  • refs/tcfs/** now classifies as a guarded non-parkable ref path, so missing-object incoming refs defer instead of raw materializing.
  • Existing .git/HEAD now classifies even when symbolic; symbolic HEAD changes and incoming detached-HEAD rewrites defer once the gitdir is initialized.
  • New packed-refs now requires object proof plus create-new semantics once the gitdir is initialized; opaque changes still defer.
  • Bootstrap raw restore is preserved only before the gitdir skeleton is ready, which addresses the git_ff_resolution CI bootstrap failure from e5398c8 without reopening initialized-gitdir raw writes.

Local validation on neo remains intentionally lightweight: cargo fmt --all -- --check and git diff --check only. Build/test validation is remote GitHub CI / fleet infrastructure.

Fix round: symbolic submodule HEAD closure (dfbd98a)

A local read-only review found one more symmetry gap before merge: .git/modules/<name>/HEAD only classified when already detached, while top-level .git/HEAD classified whenever it existed. Latest head changes submodule HEAD classification to match top-level HEAD and adds loser_guard_defers_symbolic_submodule_head_to_detached_head_pull, proving an initialized submodule symbolic HEAD defers rather than raw-overwriting to detached HEAD.

Validation boundary unchanged: only cargo fmt --all -- --check and git diff --check locally; remote CI owns build/test.

Fix round: packed-refs syntax/refname hardening (fd699a5)

Euler's clean adversarial review left one non-blocking residual: new packed-refs proved object presence but did not validate packed-refs syntax/refnames before create-new. Latest head closes that too:

  • parses each non-comment line as either SHA refs/... or peeled ^SHA;
  • requires normal refs to pass git check-ref-format and start with refs/;
  • rejects extra tokens, invalid SHAs, one-level names such as HEAD, and malformed peeled lines;
  • adds loser_guard_defers_new_malformed_packed_refs_even_when_object_exists.

Local validation remains cargo fmt --all -- --check and git diff --check only; build/test validation is remote CI.

@Jesssullivan Jesssullivan changed the title tcfs: loser-side no-loss guard for git keep-both (PR-4) tcfs: loser-side no-loss guard for git keep-both (TIN-2552) Jul 5, 2026
Add the G5-git-13 stage to the .git-as-files fsck/conflict harness — the
PASS counterpart to Stage 4's corruption-risk evidence. It reproduces the
keep-both PR-4 loser-side no-loss guard (design S10) at the ref level with
pure git on BOTH machines and asserts the no-loss invariant: after a
divergent .git pull overwrites refs/heads/main with the winner head, the
loser's committed head is parked at refs/tcfs/theirs/<self_device>/heads/*
first, so both sides' committed heads stay reachable (rev-list --all) and
each .git is fsck-clean. This is the two-machine convergence row that
flips G5-git-5 green (closes the converges-never gap).

Fail-before/pass-after verified: with the park the stage exits 0 (PASS);
neutralizing the park orphans the loser tip and the stage exits 1
("loser tip unreachable (ORPHANED)").
The loser_guard tests' upload_ref_blob helper returned UploadResult.hash
(the file-CONTENT hash) as the pull manifest_hash, but the ref blob is
retrievable only at its remote-index manifest key (manifest_object_id).
download_ref_sha_from_manifest therefore always returned None, so every
guard path fell through to the "incoming SHA unreadable" DEFER — which the
three defer-expecting tests passed on coincidentally while
loser_guard_parks_divergent_local_head_before_ref_pull (which needs the
incoming SHA to prove the non-ancestor overwrite and park) failed on CI.

Read the manifest key from list_remote_index(...).manifest_hash, matching
the working git_ff_resolution seeding pattern. All four loser_guard tests
now exercise a real incoming-SHA download: the parks test parks + applies,
the defer tests defer via their intended non-parkable/module paths.
@Jesssullivan Jesssullivan force-pushed the feat/keep-both-pr4-loser-guard branch from 614a597 to 0de8d2b Compare July 6, 2026 00:01
@Jesssullivan Jesssullivan marked this pull request as ready for review July 6, 2026 01:16
@Jesssullivan Jesssullivan merged commit 4c61da4 into main Jul 6, 2026
13 checks passed
@Jesssullivan Jesssullivan deleted the feat/keep-both-pr4-loser-guard branch July 6, 2026 01:16
@Jesssullivan

Copy link
Copy Markdown
Owner Author

Post-merge audit — PASS

4-lens adversarial pass over the final delta (6f1ae21..fd699a5, squash-merged as 4c61da4):

  1. CAS atomicity — ref pulls/deletes go through git update-ref <ref> <incoming> <expected-old> / git update-ref -d <ref> <expected-old>, not raw file writes; a live ref move between proof and mutation defers/replans instead of overwriting. Confirmed.
  2. Fallthrough closurerefs/tcfs/**, .git/HEAD (symbolic or detached), submodule HEAD (symbolic or detached), and new packed-refs all classify once the gitdir is initialized; none fall through to raw materialization. Bootstrap raw-restore path is preserved only pre-gitdir-skeleton, so it doesn't reopen the closed cases.
  3. Submodule-HEAD / packed-refs validation — symbolic submodule HEAD now matches top-level HEAD classification (defers rather than raw-overwriting to detached HEAD). New packed-refs requires object proof and syntax/refname validation (SHA refs/... / peeled ^SHA, git check-ref-format, refs/-prefixed, rejects malformed/one-level names) before create-new.
  4. Hermetic test run — 376 tests pass hermetically; coverage added for branch parking, ref deletes, non-head deferral, packed-refs deferral (top-level + submodule), missing-object/malformed-packed-refs deferral, submodule HEAD symbolic→detached deferral, exact .git path parsing, and park-ref suffix collision handling.

No correctness defects found. Tests were strengthened, not weakened. No-loss invariants confirmed: park-before-overwrite, undo-bundle-before-overwrite, CAS overwrite via git update-ref, zero-writes-on-defer.

Three minor findings tracked as follow-ups (see linked ticket): packed-refs validation subprocess-per-ref/per-SHA (no batching), redundant duplicate download/decrypt of incoming packed-refs bytes on the new-packed-refs bootstrap path, and a tcfs resolve UX gap (no --state override, unlike tcfs conflicts) surfaced during canary prep.

Jesssullivan added a commit that referenced this pull request Jul 6, 2026
…PR-4 merged) (#536)

- Add operator-executable T10/T11 divergent keep-both canary plan
  (preconditions, per-host command sequences, verification checklist,
  undo-bundle rollback, G5-git-13 flip criteria). Notes the
  tcfs-resolve-vs-isolated-state trap found during grounding.
- Update current-workstream-truth: PR-4 (#534/4c61da4, TIN-2552) is
  merged + post-merge audited; deploy + live canary still pending, so
  G5-git-13 stays red until proven.
Jesssullivan added a commit to tinyland-inc/tummycrypt that referenced this pull request Jul 6, 2026
…706-pr534-pr535

codex: sync Jesssullivan/main -> tinyland/main (Jesssullivan#534 keep-both + truth docs, 2026-07-06)
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