repository-rename: source-synced may advance the Forge head past the baseline
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:
parent
7d04c05484
commit
cfb038d76b
2 changed files with 90 additions and 13 deletions
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue