1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Close canonical cleanup race windows

This commit is contained in:
Andraxion 2026-07-29 16:29:42 -04:00
parent 1a6f33e2de
commit d2bb95fe61
4 changed files with 805 additions and 125 deletions

View file

@ -293,19 +293,24 @@ class DocForgeChangesetTests(unittest.TestCase):
proposal_path = root / ".docforge/changesets/update-race.json"
proposal_bytes = proposal_path.read_bytes()
target = root / "docs/content/workflow.md"
exchange = application_module.rename_exchange_at
exchange = application_module.rename_exchange_between_at
raced = False
def race(directory_fd: int, first: str, second: str) -> None:
def race(
first_directory_fd: int,
first: str,
second_directory_fd: int,
second: str,
) -> None:
nonlocal raced
if second == target.name and not raced:
raced = True
target.write_bytes(target.read_bytes() + b"\nExternal edit at exchange.\n")
exchange(directory_fd, first, second)
exchange(first_directory_fd, first, second_directory_fd, second)
with (
mock.patch(
"docforge.application.rename_exchange_at",
"docforge.application.rename_exchange_between_at",
side_effect=race,
),
self.assertRaises(DocForgeError) as captured,
@ -403,21 +408,26 @@ class DocForgeChangesetTests(unittest.TestCase):
],
)
delete_target = delete_root / "docs/content/proof.toml"
exchange = application_module.rename_exchange_at
exchange = application_module.rename_exchange_between_at
deleted_race = False
def race_delete(directory_fd: int, first: str, second: str) -> None:
def race_delete(
first_directory_fd: int,
first: str,
second_directory_fd: int,
second: str,
) -> None:
nonlocal deleted_race
if second == delete_target.name and not deleted_race:
deleted_race = True
delete_target.write_bytes(
delete_target.read_bytes() + b"\n# foreign delete edit\n"
)
exchange(directory_fd, first, second)
exchange(first_directory_fd, first, second_directory_fd, second)
with (
mock.patch(
"docforge.application.rename_exchange_at",
"docforge.application.rename_exchange_between_at",
side_effect=race_delete,
),
self.assertRaises(DocForgeError) as delete_error,
@ -432,6 +442,167 @@ class DocForgeChangesetTests(unittest.TestCase):
self.assertIn("# foreign delete edit", delete_target.read_text(encoding="utf-8"))
self.assertFalse(tuple(delete_target.parent.glob(".docforge-apply-*")))
def test_delete_detach_race_restores_foreign_target_and_retains_original(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture(Path(directory))
project = Project.open(root)
store = ChangesetStore(project, "alpha-editor")
proposal = store.register(
"delete-detach-race",
[
{
"operation": "delete",
"node_id": "proof.validation",
"relationship_changes": [
{
"action": "remove",
"source_id": "proof.validation",
"relation": "proves",
"target_id": "guide.workflow",
}
],
"rationale": "Race the final no-replace canonical detachment.",
}
],
)
target = root / "docs/content/proof.toml"
original = target.read_bytes()
move = application_module.rename_noreplace_between_at
raced = False
def race_detach(
source_directory_fd: int,
source: str,
target_directory_fd: int,
destination: str,
) -> bool:
nonlocal raced
if source == target.name and destination.startswith(".detached-") and not raced:
raced = True
replacement = target.with_name(".foreign-delete")
replacement.write_bytes(b"foreign replacement at delete detach\n")
os.replace(replacement, target)
return move(
source_directory_fd,
source,
target_directory_fd,
destination,
)
with (
mock.patch(
"docforge.application.rename_noreplace_between_at",
side_effect=race_detach,
),
self.assertRaises(DocForgeError) as captured,
):
store.apply(
changeset_id="delete-detach-race",
expected_changeset_hash=str(proposal["changeset_hash"]),
applier_id="alpha-editor",
application=GenericCanonicalApplier(project).apply,
)
self.assertTrue(raced)
self.assertEqual("application_recovery_required", captured.exception.code)
self.assertEqual(b"foreign replacement at delete detach\n", target.read_bytes())
conflicts = captured.exception.details["conflicts"]
retained = root / conflicts[0]["retained"]
self.assertEqual(original, retained.read_bytes())
self.assertFalse(
(root / ".docforge/changesets/.state/delete-detach-race.json").exists()
)
def test_create_rollback_detach_race_never_unlinks_foreign_target(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture(Path(directory))
project = Project.open(root)
store = ChangesetStore(project, "alpha-editor")
proposal = store.register(
"create-rollback-detach-race",
[
{
"operation": "create",
"node_id": "guide.created",
"target_source": "docs/content/a-created.md",
"metadata": self.new_metadata(),
"content": "Approved content that publishes first.",
"rationale": "Exercise create rollback detachment.",
},
{
"operation": "update",
"node_id": "guide.workflow",
"metadata": {"summary": "Synthetic failing second publication."},
"rationale": "Trigger rollback after create publication.",
},
],
)
target = root / "docs/content/a-created.md"
publish = GenericCanonicalApplier._publish
move = application_module.rename_noreplace_between_at
publish_calls = 0
raced = False
def fail_second(
applier: GenericCanonicalApplier,
publication: Any,
) -> None:
nonlocal publish_calls
publish_calls += 1
if publish_calls == 1:
publish(applier, publication)
return
raise DocForgeError("application_failure", "Synthetic second publication failure")
def race_rollback_detach(
source_directory_fd: int,
source: str,
target_directory_fd: int,
destination: str,
) -> bool:
nonlocal raced
if source == target.name and destination.startswith(".detached-") and not raced:
raced = True
replacement = target.with_name(".foreign-create-rollback")
replacement.write_bytes(b"foreign replacement during create rollback\n")
os.replace(replacement, target)
return move(
source_directory_fd,
source,
target_directory_fd,
destination,
)
with (
mock.patch.object(
GenericCanonicalApplier,
"_publish",
autospec=True,
side_effect=fail_second,
),
mock.patch(
"docforge.application.rename_noreplace_between_at",
side_effect=race_rollback_detach,
),
self.assertRaises(DocForgeError) as captured,
):
store.apply(
changeset_id="create-rollback-detach-race",
expected_changeset_hash=str(proposal["changeset_hash"]),
applier_id="alpha-editor",
application=GenericCanonicalApplier(project).apply,
)
self.assertTrue(raced)
self.assertEqual("application_recovery_required", captured.exception.code)
self.assertEqual(
b"foreign replacement during create rollback\n",
target.read_bytes(),
)
self.assertFalse(
(root / ".docforge/changesets/.state/create-rollback-detach-race.json").exists()
)
def test_rollback_never_clobbers_a_foreign_edit_and_retains_original_bytes(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture(Path(directory))
@ -505,7 +676,8 @@ class DocForgeChangesetTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture(Path(directory))
target = root / "docs/content/workflow.md"
target.chmod(0o640)
target.chmod(0o6750)
before = target.stat()
project = Project.open(root)
store = ChangesetStore(project, "alpha-editor")
proposal = store.register(
@ -528,8 +700,138 @@ class DocForgeChangesetTests(unittest.TestCase):
)
self.assertTrue(result["applied"])
self.assertEqual(0o640, stat.S_IMODE(target.stat().st_mode))
after = target.stat()
self.assertEqual(0o6750, stat.S_IMODE(after.st_mode))
self.assertEqual(before.st_uid, after.st_uid)
self.assertEqual(before.st_gid, after.st_gid)
self.assertEqual([], result["retained_recovery_files"])
self.assertEqual("clean", result["application_recovery"]["status"])
self.assertFalse(tuple((root / ".docforge/application").glob("transaction-*")))
def test_nested_creation_fsyncs_each_new_directory_and_parent_entry(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture(Path(directory))
project = Project.open(root)
store = ChangesetStore(project, "alpha-editor")
proposal = store.register(
"nested-durable",
[
{
"operation": "create",
"node_id": "guide.nested",
"target_source": "docs/content/nested/deeper/guide.md",
"metadata": self.new_metadata(),
"content": "Nested canonical content.",
"rationale": "Prove durable nested-directory creation.",
}
],
)
real_fsync = os.fsync
fsynced_directories: set[Path] = set()
def record_fsync(descriptor: int) -> None:
try:
path = Path(os.readlink(f"/proc/self/fd/{descriptor}"))
if path.is_dir():
fsynced_directories.add(path)
except OSError:
pass
real_fsync(descriptor)
with mock.patch(
"docforge.application.os.fsync",
side_effect=record_fsync,
):
result = store.apply(
changeset_id="nested-durable",
expected_changeset_hash=str(proposal["changeset_hash"]),
applier_id="alpha-editor",
application=GenericCanonicalApplier(project).apply,
)
self.assertTrue(result["applied"])
for path in (
root / "docs/content",
root / "docs/content/nested",
root / "docs/content/nested/deeper",
):
self.assertIn(path, fsynced_directories)
def test_post_commit_cleanup_failure_closes_proposal_with_recovery_record(
self,
) -> None:
with tempfile.TemporaryDirectory() as directory:
root = self.copy_fixture(Path(directory))
project = Project.open(root)
store = ChangesetStore(project, "alpha-editor")
proposal = store.register(
"cleanup-recovery",
[
{
"operation": "update",
"node_id": "guide.workflow",
"metadata": {"summary": "Committed despite private cleanup failure."},
"rationale": "Persist actionable post-commit recovery evidence.",
}
],
)
real_unlink = os.unlink
failed = False
def fail_private_cleanup(
path: str | bytes,
*,
dir_fd: int | None = None,
) -> None:
nonlocal failed
if (
isinstance(path, str)
and path.startswith("staged-")
and dir_fd is not None
and not failed
):
failed = True
raise PermissionError("synthetic private cleanup failure")
real_unlink(path, dir_fd=dir_fd)
with mock.patch(
"docforge.application.os.unlink",
side_effect=fail_private_cleanup,
):
result = store.apply(
changeset_id="cleanup-recovery",
expected_changeset_hash=str(proposal["changeset_hash"]),
applier_id="alpha-editor",
application=GenericCanonicalApplier(project).apply,
)
self.assertTrue(failed)
self.assertTrue(result["applied"])
self.assertEqual("applied", result["lifecycle"]["status"])
self.assertEqual(
"cleanup_required",
result["application_recovery"]["status"],
)
self.assertEqual(
"cleanup_required",
result["lifecycle"]["application_recovery"]["status"],
)
retained = result["application_recovery"]["retained"]
self.assertTrue(retained)
lifecycle_path = root / ".docforge/changesets/.state/cleanup-recovery.json"
lifecycle = json.loads(lifecycle_path.read_text(encoding="utf-8"))
self.assertEqual(
"cleanup_required",
lifecycle["application_recovery"]["status"],
)
with self.assertRaises(DocForgeError) as closed:
store.apply(
changeset_id="cleanup-recovery",
expected_changeset_hash=str(proposal["changeset_hash"]),
applier_id="alpha-editor",
application=GenericCanonicalApplier(project).apply,
)
self.assertEqual("changeset_closed", closed.exception.code)
def test_changeset_rollback_fsyncs_the_parent_directory(self) -> None:
with tempfile.TemporaryDirectory() as directory: