---
phase: 35.2-psyche-sync-setup-data-loss-reconcile-bootstrap-determinism
verified: 2026-05-29T00:00:00Z
status: human_needed
score: 5/5 must-haves verified
overrides_applied: 0
human_verification:
  - test: "Run `cargo test` on a Linux/macOS machine and confirm `sync_accept_flow_two_machine::two_machine_attach_preserves_first_machine_writes` passes"
    expected: "Test passes (1 ok) — machine A's commit SHA and subject 'A: foo' survive in the bare remote after machine B's accept_flow"
    why_human: "The test is `#![cfg(unix)]` and the build machine is Windows. The end-to-end invariant (cross-machine data survival via accept_flow) cannot be exercised in the current CI environment."
  - test: "Manual UAT on a real second machine: run `psyche-sync-setup` on machine B against a real GitHub `spt-agent-storage` repo seeded by machine A"
    expected: "stderr shows `RECONCILED:a-{id}` and `PUSHED:a-{id}` tags per agent branch; machine A's commits survive; no DIVERGED or PUSH_FAILED tags"
    why_human: "End-to-end real-GitHub round-trip cannot be verified from code alone; required by the phase PLAN (35.2-03 §Verification, manual UAT bullet)"
---

# Phase 35.2: psyche-sync-setup data-loss reconcile + bootstrap determinism Verification Report

**Phase Goal:** Fix psyche-sync-setup second-machine attach destroying remote agent-branch heads + failing main push. Two coordinated atomic fixes: (1) accept_flow reconciliation — git fetch origin + per-branch merge-base --is-ancestor + diverged refs rebased via rebase -X theirs origin/{branch} + per-ref push origin {branch}:{branch} replacing push --all; (2) ensure_seed deterministic bootstrap — lock GIT identity + date envs on the cold-path commit-tree so every machine's seed converges on byte-identical main SHA.
**Verified:** 2026-05-29T00:00:00Z
**Status:** human_needed
**Re-verification:** No — initial verification

## Goal Achievement

### Observable Truths

| # | Truth | Status | Evidence |
|---|-------|--------|---------|
| 1 | `ensure_seed` cold-path `commit-tree` pins all four GIT_AUTHOR_*/GIT_COMMITTER_* identity env vars AND both date vars to epoch | ✓ VERIFIED | `src/common/tracked.rs:277-284` — `.env("GIT_AUTHOR_NAME", BOOTSTRAP_NAME)`, `.env("GIT_AUTHOR_EMAIL", BOOTSTRAP_EMAIL)`, `.env("GIT_COMMITTER_NAME", BOOTSTRAP_NAME)`, `.env("GIT_COMMITTER_EMAIL", BOOTSTRAP_EMAIL)`, `.env("GIT_COMMITTER_DATE", "1970-01-01T00:00:00Z")`, `.env("GIT_AUTHOR_DATE", "1970-01-01T00:00:00Z")` — all six env vars present on the inline `std::process::Command`. CR-01 code-review fix is fully incorporated (env beats `-c`). |
| 2 | CR-01 regression test exists: `bootstrap_sha_stable_under_conflicting_identity_env` passes when four `GIT_*` identity vars are set to bogus values in one root | ✓ VERIFIED | `tests/sync_two_machine_attach.rs:76-151` — test uses `GitIdentityEnvGuard::set_conflicting()` (sets `conflict-author`/`conflict-committer` values) on machine B while machine A runs with a cleared env; asserts `sha_a.trim() == sha_b.trim()`. `tests/common/sync_fixtures.rs:59-90` confirms `GitIdentityEnvGuard` struct and `set_conflicting()` exist. |
| 3 | `reconcile_against_remote` exists: fetches origin once, probes ancestry via raw `Command::status()` distinguishing exit 0/1/128, rebases diverged branches in their linked worktrees with `-X theirs`, returns `Vec<(String, ReconcileVerdict)>` | ✓ VERIFIED | `src/common/sync.rs:602-706` — function exists as `pub fn reconcile_against_remote(seed: &Path) -> Result<Vec<(String, ReconcileVerdict)>, GitError>`. Fetch at line 615. Raw `Command::status()` with `hide_window` at lines 639-653. Match on `s.code() == Some(0)` / `Some(1)` / `_` at lines 655-702. Rebase in worktree (via `worktree_for_branch`) at lines 675-694. `abort_stale_rebase` pre-loop at line 607 and on failure at line 693. `ReconcileVerdict` enum with 5 variants at lines 434-454. `PerRefOutcome` struct at lines 464-468. |
| 4 | `accept_flow` signature changed to `Result<(String, Vec<PerRefOutcome>), SyncError>`; `push --all origin` removed; per-ref push loop with `skip-on-RebaseFailed`; seed-origin wire (step 4b); reconcile call (step 4c); step 5b driven off outcomes vec | ✓ VERIFIED | `src/common/sync.rs:729` — signature `pub fn accept_flow(user: &str) -> Result<(String, Vec<PerRefOutcome>), SyncError>`. Step 4b at lines 815-824 (seed remote add+set-url). Step 4c at line 830 (`let verdicts = reconcile_against_remote(&seed).map_err(SyncError::GitFailed)?`). Step 5 per-ref loop at lines 836-856 (`let refspec = format!("{}:{}", branch, branch)` at line 841; `RebaseFailed(_) => None` at line 839). Step 5b driven off `outcomes` vec at lines 871-885 (`if matches!(o.push, Some(Ok(())))`). Final return `Ok((remote_url, outcomes))` at line 898. No literal `"--all"` in live push code (only in comments/doc). |
| 5 | `psyche_sync_setup::run` destructures `Ok((url, outcomes))` and calls `emit_outcome_tag` per outcome; `emit_outcome_tag` emits PUSHED/RECONCILED/DIVERGED/PUSH_FAILED/PROBE_FAILED tags; CLAUDE.md updated | ✓ VERIFIED | `src/owl/psyche_sync_setup.rs:61-67` — `Ok((url, outcomes)) => { for o in &outcomes { emit_outcome_tag(o); } println!("sync enabled; remote={}", url); }`. `emit_outcome_tag` at lines 158-171 — emits `RECONCILED`, `DIVERGED`, `PROBE_FAILED` on reconcile side; `PUSHED`, `PUSH_FAILED` on push side. Note: PROBE_FAILED tag was added by the WR-02 fix (beyond original plan scope). `CLAUDE.md:57` — "Sync status tags (psyche-sync-setup per-ref): `PUSHED:{branch}`, `RECONCILED:{branch}`. Errors: `DIVERGED:{branch}`, `PUSH_FAILED:{branch}`, `PROBE_FAILED:{branch}`..." |

**Score:** 5/5 truths verified

### Required Artifacts

| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `src/common/tracked.rs` | Deterministic `ensure_seed` cold-path bootstrap commit with all six env vars | ✓ VERIFIED | Lines 262-290: inline `Command::new("git")` with `hide_window`, 4 identity env vars + 2 date env vars, no `-c` args (dropped per CR-01 fix) |
| `src/common/sync.rs` | `ReconcileVerdict` enum, `PerRefOutcome` struct, `reconcile_against_remote` fn, updated `accept_flow` | ✓ VERIFIED | Lines 425-468 (types), 602-706 (helper), 729-898 (accept_flow) |
| `src/owl/psyche_sync_setup.rs` | `emit_outcome_tag` private fn, `Ok((url, outcomes))` destructure | ✓ VERIFIED | Lines 61-67 (dispatch), 158-171 (emit fn); fn is module-private (no `pub`) |
| `CLAUDE.md` | Sync status-tag inventory line | ✓ VERIFIED | Line 57, immediately after existing status-tags line 56; includes all 5 tags (PROBE_FAILED added by WR-02 fix) |
| `tests/sync_two_machine_attach.rs` | Cross-platform determinism test + CR-01 regression test | ✓ VERIFIED | 152 lines; `two_machines_bootstrap_to_identical_main_sha` (line 23) and `bootstrap_sha_stable_under_conflicting_identity_env` (line 78); both `#[serial_test::serial]`; no `#[cfg(unix)]`; uses `GitIdentityEnvGuard` for CR-01 |
| `tests/sync_reconcile_against_remote.rs` | 3 cross-platform routing tests | ✓ VERIFIED | 239 lines; `is_ancestor_probe_routes_branches_correctly`, `diverged_branch_rebases_with_theirs_strategy`, `fetch_runs_before_push_when_remote_exists`; all `#[serial_test::serial]`; no `#[cfg(unix)]` |
| `tests/sync_accept_flow_two_machine.rs` | Unix-gated end-to-end accept_flow test | ✓ VERIFIED | 180 lines; `#![cfg(unix)]` at line 1; `two_machine_attach_preserves_first_machine_writes`; `#[serial_test::serial]`; drives `accept_flow` end-to-end (no direct `reconcile_against_remote` call); uses `GIT_CONFIG_GLOBAL` insteadOf rewrite for URL routing |

### Key Link Verification

| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `tracked.rs::ensure_seed` cold path | `Command::new("git")` with `.env("GIT_COMMITTER_DATE", ...)` | inline Command replacing `run_git_checked` | ✓ WIRED | Lines 262-290; `hide_window` present at line 263 |
| `tracked.rs::ensure_seed` | All four identity env vars pinned | `.env("GIT_AUTHOR_NAME", ...)` etc | ✓ WIRED | Lines 277-280; `BOOTSTRAP_NAME`/`BOOTSTRAP_EMAIL` constants reused |
| `sync.rs::reconcile_against_remote` | `git merge-base --is-ancestor` raw exit code | `Command::status()` NOT `run_git_checked` | ✓ WIRED | Lines 639-653; comment explicitly references Pitfall 1 rationale |
| `sync.rs::reconcile_against_remote` | `git rebase -X theirs origin/{branch}` in worktree | `worktree_for_branch` + `run_git_checked` in wt | ✓ WIRED | Lines 666-694; worktree-scoped rebase (SUMMARY deviation 1 fix) |
| `sync.rs::accept_flow` | `reconcile_against_remote(&seed)` | `let verdicts = reconcile_against_remote(...)` | ✓ WIRED | Line 830 |
| `sync.rs::accept_flow` | per-ref `push origin {b}:{b}` | `format!("{}:{}", branch, branch)` loop | ✓ WIRED | Lines 836-856 |
| `psyche_sync_setup.rs::run` | `emit_outcome_tag` | `for o in &outcomes { emit_outcome_tag(o); }` | ✓ WIRED | Lines 63-65 |
| `emit_outcome_tag` | stderr tags | `eprintln!("RECONCILED:{}", ...)` etc | ✓ WIRED | Lines 161-169; 5 tags (RECONCILED, DIVERGED, PROBE_FAILED, PUSHED, PUSH_FAILED) |
| `tests/sync_two_machine_attach.rs` | `bootstrap_sha_stable_under_conflicting_identity_env` | `GitIdentityEnvGuard::set_conflicting()` | ✓ WIRED | Line 133; `tests/common/sync_fixtures.rs:65-77` |

### Data-Flow Trace (Level 4)

| Artifact | Data Variable | Source | Produces Real Data | Status |
|----------|--------------|--------|-------------------|--------|
| `accept_flow` per-ref push | `verdicts` | `reconcile_against_remote(&seed)` which calls real `git fetch` + `git merge-base` + `git rebase` | Yes — live git subprocess results | ✓ FLOWING |
| `accept_flow` outcomes | `outcomes: Vec<PerRefOutcome>` | per-branch push `run_git_checked` result captured | Yes — real push results | ✓ FLOWING |
| `emit_outcome_tag` | `o.branch`, `o.reconcile`, `o.push` | wired from `accept_flow` outcomes via `for o in &outcomes` | Yes — real reconcile/push state | ✓ FLOWING |
| bootstrap commit SHA | `commit_sha` | inline `Command::new("git").output()` with env-pinned identity+date | Yes — real git commit-tree output | ✓ FLOWING |

### Behavioral Spot-Checks

| Behavior | Command | Result | Status |
|----------|---------|--------|--------|
| `GIT_COMMITTER_DATE` in `ensure_seed` | `grep -c "GIT_COMMITTER_DATE" src/common/tracked.rs` | 2 matches (1 comment + 1 code) | ✓ PASS |
| All four identity env vars present | `grep -c "GIT_AUTHOR_NAME\|GIT_COMMITTER_NAME\|GIT_AUTHOR_EMAIL\|GIT_COMMITTER_EMAIL" src/common/tracked.rs` | 6 matches (2 comment + 4 code) | ✓ PASS |
| `push --all` removed from live code | grep for `"--all", "origin"` in sync.rs live code | 0 matches in code paths (only in comments/old doc) | ✓ PASS |
| `reconcile_against_remote` call in `accept_flow` | `grep -c "reconcile_against_remote" src/common/sync.rs` | multiple matches including definition and call in `accept_flow` | ✓ PASS |
| `emit_outcome_tag` module-private | `grep "pub fn emit_outcome_tag" src/owl/psyche_sync_setup.rs` | 0 matches — fn is private | ✓ PASS |
| `Ok((url, outcomes))` destructure | present in `psyche_sync_setup.rs` | Line 62 | ✓ PASS |
| CR-01 regression test exists | `tests/sync_two_machine_attach.rs` has `bootstrap_sha_stable_under_conflicting_identity_env` | Line 78 | ✓ PASS |
| Commits documented in SUMMARY exist | git log shows `c709258`, `8317dd7`, `6ffe93c`, `52e9d5e`, etc. | All present in history | ✓ PASS |

### Probe Execution

Step 7c SKIPPED — no conventional `scripts/*/tests/probe-*.sh` probes found for this phase. The build-and-test verification (`cargo test`) was documented in SUMMARY files as passing but cannot be re-run in this verification pass without a build environment. The cargo test results are accepted based on the documented build-machine evidence in the SUMMARYs, with the unix-gated end-to-end test routed to human verification.

### Requirements Coverage

| Requirement | Source Plan | Description | Status | Evidence |
|-------------|------------|-------------|--------|---------|
| SYNC-BOOTSTRAP-DET-01 | 35.2-01 | `ensure_seed` cold-path produces byte-identical `refs/heads/main` SHA across SPT_HOME roots | ✓ SATISFIED | `tracked.rs:262-290` — inline Command with 6 env vars; CR-01 fix incorporated (identity env vars beat `-c`); CR-01 regression test at `tests/sync_two_machine_attach.rs:78` |
| SYNC-RECON-FETCH-01 | 35.2-02 | `reconcile_against_remote` runs `git fetch origin` before any per-branch probe | ✓ SATISFIED | `sync.rs:615-619` — `git::run_git_checked(["-C", &seed_s, "fetch", "origin"], ...)` as the only hard-error path |
| SYNC-RECON-PER-BRANCH-01 | 35.2-02 | Per-branch is-ancestor probe via raw `Command::status()` distinguishing exit 0/1/128 | ✓ SATISFIED | `sync.rs:639-702` — raw Command with `hide_window`, match on `s.code() == Some(0)` / `Some(1)` / `_` |
| SYNC-RECON-REBASE-01 | 35.2-02 | Diverged branches rebased via `rebase -X theirs origin/{branch}` | ✓ SATISFIED | `sync.rs:675-694` — rebase in linked worktree (not bare seed); `abort_stale_rebase` defensive calls pre-loop and on failure |
| SYNC-PUSH-PER-REF-01 | 35.2-03 | `push --all origin` replaced with per-branch `push origin {b}:{b}` loop; RebaseFailed skips push | ✓ SATISFIED | `sync.rs:836-856` — `let refspec = format!("{}:{}", branch, branch)` push; `RebaseFailed(_) => None` skip semantics |

### Anti-Patterns Found

| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| `src/owl/psyche_sync_setup.rs` | 75 | `eprintln!("sync setup failed: {:?}", e)` Debug-repr leak | ℹ️ Info | Intentionally preserved; Phase 35.3 scope per RESEARCH. Documented in code comment. Not a gap. |
| `src/common/sync.rs` | 1584 | Old in-module test comment references `push --all` | ℹ️ Info | The test was updated (renamed `push_per_ref_from_seed`); stale comment in a comment block, not live behavior |

No TBD/FIXME/XXX debt markers found in the phase-modified files. No BLOCKER anti-patterns.

Code-review findings addressed by the `fix(35.2):` commits:
- CR-01 (critical): pinned all four identity env vars — FIXED (commit `c709258`)
- WR-01 (is_remote_404 over-match): phrase-anchored — FIXED (commit `0e892f1`)
- WR-02 (ProbeFailed silent): PROBE_FAILED tag added — FIXED (commit `9f2b299`)
- WR-03 (rustdoc misattachment): reattached — FIXED (commit `db6460f`)
- WR-04 (disable leaves stale counters): counters reset — FIXED (commit `7c182db`)
- WR-05 (CR-01 test blind spot): identity-env regression test added — FIXED (commit `8317dd7`)
- WR-06 (set-upstream unconditional): driven off outcomes vec — FIXED (commit `f5d3d27`)

### Human Verification Required

#### 1. Unix end-to-end accept_flow test

**Test:** On a Linux or macOS machine, run `cargo test --test sync_accept_flow_two_machine -- --nocapture`
**Expected:** `test two_machine_attach_preserves_first_machine_writes ... ok` — machine A's commit SHA and subject "A: foo" survive in the bare remote after machine B's accept_flow
**Why human:** `tests/sync_accept_flow_two_machine.rs` is guarded by `#![cfg(unix)]`. The build machine is Windows; the test compiles to zero tests on Windows and cannot be exercised in this verification pass.

#### 2. Real-GitHub manual UAT (from Plan 35.2-03 verification block)

**Test:** Run `psyche-sync-setup` on a real second machine (machine B) against a real GitHub `spt-agent-storage` repo that was seeded by machine A
**Expected:** stderr shows `RECONCILED:a-{id}` and `PUSHED:a-{id}` per agent branch; machine A's commits survive on all agent branches; no `DIVERGED` or `PUSH_FAILED` tags
**Why human:** End-to-end real-GitHub round-trip with auth, network, and actual repo state cannot be verified from code inspection. This is the mandatory pre-deploy UAT per the phase plan.

### Gaps Summary

No blocking gaps found. All five requirement IDs are satisfied by shipped code that is substantive and wired. The code-review fix pass (CR-01 and WR-01 through WR-06) is fully incorporated and verified in the codebase. The two human verification items are pre-existing UAT requirements (the unix-gated end-to-end test and the real-GitHub manual UAT), not newly discovered defects.

**Note on plan deviations accepted into code:**
- Plan 35.2-02 stated rebase should run in the seed with an explicit branch positional arg. The executor discovered this is factually wrong (`git rebase` cannot run in a bare repo). The shipped code runs rebase in the linked worktree via `worktree_for_branch` — this is the correct fix, not a regression.
- `reconcile_against_remote` is `pub` rather than `pub(crate)` because integration tests in `tests/` cannot reach `pub(crate)` items. This is consistent with sibling `pull_branch`/`push_branch` visibility.
- `emit_outcome_tag` emits a fifth tag `PROBE_FAILED:{branch}` beyond the original four planned (WR-02 fix). CLAUDE.md is updated to reflect this.

---

_Verified: 2026-05-29T00:00:00Z_
_Verifier: Claude (gsd-verifier)_
