repository-rename: source-synced may advance the Forge head past the baseline
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 27s

The runbook pushes a source-synchronization revision before source-synced, but
the phase required the head to still equal the preflight baseline. Anchor later
phases on the head recorded by source-synced; require the clone to match Forge.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 101414@bnt-lap001
Assistant-Session: 60b5a221-d435-42b9-b778-983c90c7eed6
This commit is contained in:
tegwick 2026-09-30 18:08:14 +02:00
parent 7d04c05484
commit cfb038d76b
2 changed files with 90 additions and 13 deletions

View file

@ -1052,12 +1052,28 @@ async def list_operations(
return list(result.scalars().all())
def _expected_head(operation: RepositoryRenameOperation) -> str:
"""Head the Forge default branch must show at this point in the journal.
The preflight baseline anchors every phase up to ``statehub-rebound``.
``source-synced`` legitimately advances the head (the source-synchronization
revision and fix-consistency writeback are pushed from the fresh clone), so
once that phase is journalled its recorded Forge head is the anchor.
"""
journal, _ = _telemetry(operation)
synced = (journal.get("phases") or {}).get("source-synced") or {}
head = (synced.get("forge") or {}).get("head_commit")
return head or operation.expected_source_commit
def _assert_snapshot(
operation: RepositoryRenameOperation,
snapshot: ForgeRepositorySnapshot,
*,
expected_name: str | None = None,
expected_head: str | None = None,
) -> None:
expected_head = expected_head or _expected_head(operation)
if snapshot.repository_id != operation.expected_forge_repository_id:
raise RenamePreconditionFailed(
"Forge repository ID changed",
@ -1068,11 +1084,11 @@ def _assert_snapshot(
)
if snapshot.default_branch != operation.expected_default_branch:
raise RenamePreconditionFailed("Forge default branch changed")
if snapshot.head_commit != operation.expected_source_commit:
if snapshot.head_commit != expected_head:
raise RenamePreconditionFailed(
"Forge default-branch head moved",
details={
"expected": operation.expected_source_commit,
"expected": expected_head,
"actual": snapshot.head_commit,
},
)
@ -1232,7 +1248,7 @@ async def verify_operation(
if forge is not None:
add("forge_coordinate", forge.name == expected_name, expected_name, forge.name)
add("forge_repository_id", forge.repository_id == operation.expected_forge_repository_id, operation.expected_forge_repository_id, forge.repository_id)
add("source_commit", forge.head_commit == operation.expected_source_commit, operation.expected_source_commit, forge.head_commit)
add("source_commit", forge.head_commit == _expected_head(operation), _expected_head(operation), forge.head_commit)
add("default_branch", forge.default_branch == operation.expected_default_branch, operation.expected_default_branch, forge.default_branch)
add("projection_readable", forge.projection_readable and forge.projection_source_present, True, forge.projection_readable and forge.projection_source_present)
expected_canonical = operation.old_slug if operation.phase in {"preflighted", "forge-renamed", "rolled-back"} else operation.new_slug
@ -1503,19 +1519,29 @@ async def apply_phase(
},
)
clone_head = body.evidence.get("head_commit")
if clone_head != operation.expected_source_commit:
raise RenamePreconditionFailed(
"Fresh clone head does not match the rename baseline",
details={
"expected": operation.expected_source_commit,
"actual": clone_head,
},
)
forge = await _forge_at(gateway, operation, operation.new_slug)
if forge is None:
raise RenamePreconditionFailed("Renamed Forge repository is absent")
_assert_snapshot(operation, forge, expected_name=operation.new_slug)
evidence = {"forge": forge.as_dict(), "operator_evidence": body.evidence}
if clone_head != forge.head_commit:
raise RenamePreconditionFailed(
"Fresh clone head does not match the Forge default-branch head",
details={
"expected": forge.head_commit,
"actual": clone_head,
"baseline": operation.expected_source_commit,
},
)
# The head may have advanced past the baseline through the
# source-synchronization revision; it becomes the new anchor.
_assert_snapshot(
operation, forge, expected_name=operation.new_slug, expected_head=clone_head
)
evidence = {
"forge": forge.as_dict(),
"baseline_commit": operation.expected_source_commit,
"head_advanced": forge.head_commit != operation.expected_source_commit,
"operator_evidence": body.evidence,
}
elif requested_phase == "consumers-verified":
if not body.checks or not all(body.checks.values()):
raise RenamePreconditionFailed(