fi-research-brief: rebase once onto a moved origin instead of stranding briefs
Some checks failed
Governed runtime contract / contract (push) Failing after 33s
Some checks failed
Governed runtime contract / contract (push) Failing after 33s
Every push from 2026-09-15 to 2026-09-22 was rejected non-fast-forward because origin had moved; six briefs stayed local on railiance01. Per the FI publication decision (2026-09-22), fetch, confirm that unpublished commits touch only briefs/, rebase once and retry. Never merge or force; a conflict aborts the rebase and fails the day. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 51320@bnt-lap001 Assistant-Session: 9d40b4c7-8e3c-42ee-b755-d658d4640d6c
This commit is contained in:
parent
364bed78b2
commit
294201a367
2 changed files with 99 additions and 2 deletions
|
|
@ -351,15 +351,48 @@ def _git(repo: Path, *args: str, timeout: int = 60) -> str:
|
|||
return result.stdout.strip()
|
||||
|
||||
|
||||
_PUBLISH_PREFIX = "briefs/"
|
||||
|
||||
|
||||
def _publish_origin(repo: Path) -> str:
|
||||
"""Push HEAD to origin. FI-owned grant: origin is the durable brief store."""
|
||||
"""Push HEAD to origin. FI-owned grant: origin is the durable brief store.
|
||||
|
||||
FI decision 2026-09-22 (docs/decisions/2026-09-22-brief-origin-publication.md):
|
||||
fast-forward only. If origin moved, rebase the unpublished brief commits
|
||||
onto it once and retry. Never merge, never force.
|
||||
"""
|
||||
branch = _git(repo, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
if not branch or branch == "HEAD":
|
||||
branch = "main"
|
||||
_git(repo, "push", "-u", "origin", f"HEAD:{branch}", timeout=120)
|
||||
try:
|
||||
_git(repo, "push", "-u", "origin", f"HEAD:{branch}", timeout=120)
|
||||
except FiResearchBriefError:
|
||||
_rebase_onto_origin(repo, branch)
|
||||
_git(repo, "push", "-u", "origin", f"HEAD:{branch}", timeout=120)
|
||||
return _git(repo, "rev-parse", "HEAD")
|
||||
|
||||
|
||||
def _rebase_onto_origin(repo: Path, branch: str) -> None:
|
||||
"""Replay local brief-only commits onto origin/<branch>, or fail closed."""
|
||||
_git(repo, "fetch", "origin", branch, timeout=120)
|
||||
upstream = f"origin/{branch}"
|
||||
changed = _git(repo, "diff", "--name-only", f"{upstream}...HEAD").splitlines()
|
||||
outside = [p for p in changed if not p.startswith(_PUBLISH_PREFIX)]
|
||||
if outside:
|
||||
raise FiResearchBriefError(
|
||||
f"unpublished commits touch paths outside {_PUBLISH_PREFIX}: {outside[:5]}"
|
||||
)
|
||||
try:
|
||||
_git(repo, "rebase", upstream, timeout=120)
|
||||
except FiResearchBriefError:
|
||||
subprocess.run(
|
||||
["git", "-C", str(repo), "rebase", "--abort"],
|
||||
capture_output=True,
|
||||
timeout=60,
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
def run_fi_research_brief(
|
||||
target_repo: Path,
|
||||
*,
|
||||
|
|
|
|||
|
|
@ -104,3 +104,67 @@ def test_fi_brief_push_failure_is_not_a_completed_day(
|
|||
assert posted
|
||||
assert posted[-1]["event_type"] == "executor_run"
|
||||
assert posted[-1]["detail"]["pushed"] is False
|
||||
|
||||
|
||||
def _advance_origin(tmp_path: Path, name: str, rel: str) -> None:
|
||||
"""Commit to origin from a second clone so the executor's push is non-fast-forward."""
|
||||
other = tmp_path / name
|
||||
subprocess.run(["git", "clone", "-q", str(tmp_path / "origin.git"), str(other)], check=True)
|
||||
(other / rel).parent.mkdir(parents=True, exist_ok=True)
|
||||
(other / rel).write_text("moved\n", encoding="utf-8")
|
||||
_git(other, "add", rel)
|
||||
_git(other, "-c", "user.email=o@example.invalid", "-c", "user.name=o", "commit", "-qm", "move origin")
|
||||
_git(other, "push", "-q", "origin", "HEAD:main")
|
||||
|
||||
|
||||
def test_fi_brief_rebases_once_when_origin_moved(tmp_path: Path) -> None:
|
||||
repo = _repo(tmp_path)
|
||||
_with_origin(tmp_path, repo)
|
||||
_advance_origin(tmp_path, "other", "WORK-RECORDS.md")
|
||||
result = fi_research_brief.run_fi_research_brief(
|
||||
repo, day=date(2026, 9, 22), report_to_hub=False, complete_fn=_complete
|
||||
)
|
||||
assert result.ok is True
|
||||
assert result.pushed is True
|
||||
remote_head = subprocess.run(
|
||||
["git", "-C", str(tmp_path / "origin.git"), "rev-parse", "HEAD"],
|
||||
check=True, capture_output=True, text=True,
|
||||
).stdout.strip()
|
||||
assert remote_head == result.origin_sha
|
||||
parents = subprocess.run(
|
||||
["git", "-C", str(repo), "rev-list", "--parents", "-1", "HEAD"],
|
||||
check=True, capture_output=True, text=True,
|
||||
).stdout.split()
|
||||
assert len(parents) == 2 # linear history: rebased, not merged
|
||||
|
||||
|
||||
def test_fi_brief_refuses_rebase_of_non_brief_commits(tmp_path: Path) -> None:
|
||||
repo = _repo(tmp_path)
|
||||
_with_origin(tmp_path, repo)
|
||||
(repo / "notes.md").write_text("local only\n", encoding="utf-8")
|
||||
_git(repo, "add", "notes.md")
|
||||
_git(repo, "-c", "user.email=t@example.invalid", "-c", "user.name=t", "commit", "-qm", "stray")
|
||||
_advance_origin(tmp_path, "other", "WORK-RECORDS.md")
|
||||
result = fi_research_brief.run_fi_research_brief(
|
||||
repo, day=date(2026, 9, 22), report_to_hub=False, complete_fn=_complete
|
||||
)
|
||||
assert result.ok is False
|
||||
assert result.pushed is False
|
||||
assert "outside briefs/" in result.reason
|
||||
|
||||
|
||||
def test_fi_brief_rebase_conflict_fails_closed(tmp_path: Path) -> None:
|
||||
repo = _repo(tmp_path)
|
||||
_with_origin(tmp_path, repo)
|
||||
_advance_origin(tmp_path, "other", "briefs/2026/09/2026-09-22.md")
|
||||
result = fi_research_brief.run_fi_research_brief(
|
||||
repo, day=date(2026, 9, 22), report_to_hub=False, complete_fn=_complete
|
||||
)
|
||||
assert result.ok is False
|
||||
assert result.pushed is False
|
||||
status = subprocess.run(
|
||||
["git", "-C", str(repo), "status", "--porcelain=v2", "--branch"],
|
||||
check=True, capture_output=True, text=True,
|
||||
).stdout
|
||||
assert "rebase" not in status.lower()
|
||||
assert not (repo / ".git" / "rebase-merge").exists()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue