sched: sibling-distinct lock identity; reject shared-store overlaps (R4 wave 4)
This commit is contained in:
@@ -205,3 +205,90 @@ def test_canonical_windows_path_handling_retained(tmp_path: Path) -> None:
|
||||
assert canonical_store_path("C:\\Temp\\SCHED") == canonical_store_path(
|
||||
"c:/temp/sched"
|
||||
)
|
||||
|
||||
|
||||
def test_non_sibling_store_pairs_have_no_lock_identity(tmp_path: Path) -> None:
|
||||
"""Only distinct siblings under one composition root share a lock file.
|
||||
|
||||
Cross pairs that reuse one protected store (shared schedule store or
|
||||
shared run store), nested pairs, and same-directory dual use would map
|
||||
to different lock files while covering the same store files, so they
|
||||
have no lock identity at all: guards must reject them outright.
|
||||
"""
|
||||
overlap = tmp_path / "overlap"
|
||||
sched_store, run_store = _stores(overlap)
|
||||
other_sched, other_runs = _stores(tmp_path / "other")
|
||||
# Shared schedule store, different run store.
|
||||
assert canonical_lock_root(sched_store.root, other_runs.root) is None
|
||||
# Shared run store, different schedule store.
|
||||
assert canonical_lock_root(other_sched.root, run_store.root) is None
|
||||
# Nested pairs: one store inside the other.
|
||||
assert canonical_lock_root(overlap, sched_store.root) is None
|
||||
assert canonical_lock_root(sched_store.root, overlap) is None
|
||||
# Same directory serving as both stores.
|
||||
assert canonical_lock_root(sched_store.root, sched_store.root) is None
|
||||
|
||||
|
||||
def test_shared_schedule_store_cannot_gain_independent_authority(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
overlap = tmp_path / "overlap"
|
||||
sched_store, run_store = _stores(overlap)
|
||||
_, other_runs = _stores(tmp_path / "other")
|
||||
owner = SchedulerOwnership(overlap, owner="owner").acquire()
|
||||
intruder = SchedulerOwnership(tmp_path, owner="intruder").acquire()
|
||||
try:
|
||||
assert owner.covers(sched_store.root, run_store.root) is True
|
||||
# The cross pair reuses the protected schedule store but maps to
|
||||
# no lock identity, so no held lock can authorize it.
|
||||
assert intruder.covers(sched_store.root, other_runs.root) is False
|
||||
assert owner.covers(sched_store.root, other_runs.root) is False
|
||||
now = datetime.now(UTC)
|
||||
sched_recovery.recover(
|
||||
schedule_store=sched_store,
|
||||
run_store=run_store,
|
||||
now=now,
|
||||
ownership=owner,
|
||||
)
|
||||
before_runs = FileRunStore(overlap / "runs").list_runs()
|
||||
with pytest.raises(SecondOwnerError):
|
||||
sched_recovery.recover(
|
||||
schedule_store=FileScheduleStore(overlap / "sched"),
|
||||
run_store=FileRunStore(tmp_path / "other" / "runs"),
|
||||
now=now,
|
||||
ownership=intruder,
|
||||
)
|
||||
# Rejection happens before any write to either store.
|
||||
assert FileRunStore(overlap / "runs").list_runs() == before_runs
|
||||
assert FileRunStore(tmp_path / "other" / "runs").list_runs() == []
|
||||
assert FileRunStore(tmp_path / "other" / "runs").list_admissions() == []
|
||||
finally:
|
||||
owner.release()
|
||||
intruder.release()
|
||||
|
||||
|
||||
def test_shared_run_store_cannot_gain_independent_authority(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
overlap = tmp_path / "overlap"
|
||||
sched_store, run_store = _stores(overlap)
|
||||
other_sched, _ = _stores(tmp_path / "other")
|
||||
owner = SchedulerOwnership(overlap, owner="owner").acquire()
|
||||
intruder = SchedulerOwnership(tmp_path, owner="intruder").acquire()
|
||||
try:
|
||||
assert owner.covers(sched_store.root, run_store.root) is True
|
||||
assert intruder.covers(other_sched.root, run_store.root) is False
|
||||
assert owner.covers(other_sched.root, run_store.root) is False
|
||||
now = datetime.now(UTC)
|
||||
with pytest.raises(SecondOwnerError):
|
||||
sched_recovery.recover(
|
||||
schedule_store=FileScheduleStore(tmp_path / "other" / "sched"),
|
||||
run_store=FileRunStore(overlap / "runs"),
|
||||
now=now,
|
||||
ownership=intruder,
|
||||
)
|
||||
assert FileRunStore(overlap / "runs").list_runs() == []
|
||||
assert FileRunStore(overlap / "runs").list_admissions() == []
|
||||
finally:
|
||||
owner.release()
|
||||
intruder.release()
|
||||
|
||||
Reference in New Issue
Block a user