fix(dwh): bind snapshots to validated bytes
This commit is contained in:
@@ -18,3 +18,13 @@ ACTIVE publication, and retention cleanup. Each test proves the replacement tree
|
|||||||
written, or deleted; the descriptor-pinned original either completes consistently or fails closed.
|
written, or deleted; the descriptor-pinned original either completes consistently or fails closed.
|
||||||
Existing owner binding, legacy rejection, crash reconciliation, resume, atomic rollback, retention,
|
Existing owner binding, legacy rejection, crash reconciliation, resume, atomic rollback, retention,
|
||||||
and reader/writer exclusion behavior remains covered.
|
and reader/writer exclusion behavior remains covered.
|
||||||
|
|
||||||
|
## Final review correction
|
||||||
|
|
||||||
|
Snapshot materialization now reads the manifest and every owned artifact exactly once through the
|
||||||
|
already-open generation descriptor, validates each hash against those exact bytes, and writes the
|
||||||
|
same byte objects to the private snapshot. A deterministic second-read mutation test proves hostile
|
||||||
|
pickle bytes can neither pass validation nor enter the snapshot. Reconciliation closes the ACTIVE
|
||||||
|
generation descriptor in a `finally` block on matches, mismatches, and exceptions. Pipeline-owned
|
||||||
|
snapshot directories are removed and deregistered after `run_job` on both successful and failed
|
||||||
|
runs, preventing repeated pipeline use from accumulating temporary directories or registry entries.
|
||||||
|
|||||||
@@ -524,6 +524,90 @@ def test_snapshot_root_swap_after_lease_never_reads_replacement(monkeypatch, tmp
|
|||||||
assert (replacement / "sentinel").read_text() == "replacement-secret"
|
assert (replacement / "sentinel").read_text() == "replacement-secret"
|
||||||
|
|
||||||
|
|
||||||
|
def test_snapshot_copies_each_validated_artifact_once_without_reopen(monkeypatch, tmp_path):
|
||||||
|
import tht.jobs.dwh_pipeline as module
|
||||||
|
|
||||||
|
pipeline = DwhPreprocessPipeline(
|
||||||
|
workspace_id="demo", workspace_root=tmp_path,
|
||||||
|
config_fingerprint=FP, input_fingerprint=FP,
|
||||||
|
introspect=lambda output: output.write_text("trusted"),
|
||||||
|
build_lsh=lambda physical, output: [
|
||||||
|
(output / name).write_text("trusted")
|
||||||
|
for name in ("demo_lsh.pkl", "demo_minhashes.pkl", "demo_meta.json")
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert pipeline.run().status == "succeeded"
|
||||||
|
real_read = module._read_owned_at
|
||||||
|
reads = {}
|
||||||
|
|
||||||
|
def mutate_on_reopen(directory_fd, name, *, readonly):
|
||||||
|
reads[name] = reads.get(name, 0) + 1
|
||||||
|
if name.endswith(".pkl") and reads[name] > 1:
|
||||||
|
return b"MALICIOUS_PICKLE"
|
||||||
|
return real_read(directory_fd, name, readonly=readonly)
|
||||||
|
|
||||||
|
monkeypatch.setattr(module, "_read_owned_at", mutate_on_reopen)
|
||||||
|
with lease_dwh_snapshot(snapshot_config(tmp_path)) as snapshot:
|
||||||
|
assert (snapshot.lsh_dir / "demo_lsh.pkl").read_text() == "trusted"
|
||||||
|
assert "MALICIOUS" not in (snapshot.lsh_dir / "demo_lsh.pkl").read_text()
|
||||||
|
assert all(count == 1 for count in reads.values())
|
||||||
|
|
||||||
|
|
||||||
|
def test_reconcile_mismatch_closes_active_generation_fd(monkeypatch, tmp_path):
|
||||||
|
import os
|
||||||
|
from types import SimpleNamespace
|
||||||
|
import pytest
|
||||||
|
import tht.jobs.dwh_pipeline as module
|
||||||
|
|
||||||
|
pipeline = DwhPreprocessPipeline(
|
||||||
|
workspace_id="demo", workspace_root=tmp_path,
|
||||||
|
config_fingerprint=FP, input_fingerprint=FP,
|
||||||
|
introspect=lambda output: output.write_text("catalog"),
|
||||||
|
build_lsh=lambda physical, output: _write_lsh([], physical, output),
|
||||||
|
)
|
||||||
|
report = pipeline.run()
|
||||||
|
run_dir = tmp_path / ".tht-jobs" / "dwh" / "runs" / report.run_id
|
||||||
|
real_active = module._active_generation_fd
|
||||||
|
|
||||||
|
def mismatched_active(root_fd, binding):
|
||||||
|
generation, generation_fd = real_active(root_fd, binding)
|
||||||
|
return "f" * 32, generation_fd
|
||||||
|
|
||||||
|
monkeypatch.setattr(module, "_active_generation_fd", mismatched_active)
|
||||||
|
source = SimpleNamespace(
|
||||||
|
run_id=report.run_id,
|
||||||
|
stages=(SimpleNamespace(
|
||||||
|
status="running", effect_state="intent", name="lsh",
|
||||||
|
artifact_files=("physical.yaml", "demo_lsh.pkl", "demo_minhashes.pkl",
|
||||||
|
"demo_meta.json"),
|
||||||
|
),),
|
||||||
|
)
|
||||||
|
before = len(os.listdir("/dev/fd"))
|
||||||
|
with pytest.raises(Exception, match="not ACTIVE"):
|
||||||
|
pipeline._reconcile_effects(source, run_dir)
|
||||||
|
assert len(os.listdir("/dev/fd")) == before
|
||||||
|
|
||||||
|
|
||||||
|
def test_pipeline_releases_materialized_snapshot_after_every_run(tmp_path):
|
||||||
|
import tht.jobs.dwh_pipeline as module
|
||||||
|
|
||||||
|
pipeline = DwhPreprocessPipeline(
|
||||||
|
workspace_id="demo", workspace_root=tmp_path,
|
||||||
|
config_fingerprint=FP, input_fingerprint=FP,
|
||||||
|
introspect=lambda output: output.write_text("catalog"),
|
||||||
|
build_lsh=lambda physical, output: _write_lsh([], physical, output),
|
||||||
|
)
|
||||||
|
baseline = set(module._SNAPSHOT_DIRS)
|
||||||
|
for _ in range(3):
|
||||||
|
assert pipeline.run().status == "succeeded"
|
||||||
|
assert set(module._SNAPSHOT_DIRS) == baseline
|
||||||
|
assert pipeline._snapshot_holder is None
|
||||||
|
pipeline.introspect = lambda output: (_ for _ in ()).throw(RuntimeError("injected"))
|
||||||
|
assert pipeline.run().status == "failed"
|
||||||
|
assert set(module._SNAPSHOT_DIRS) == baseline
|
||||||
|
assert pipeline._snapshot_holder is None
|
||||||
|
|
||||||
|
|
||||||
def test_publish_root_swap_after_lease_never_writes_replacement(monkeypatch, tmp_path):
|
def test_publish_root_swap_after_lease_never_writes_replacement(monkeypatch, tmp_path):
|
||||||
import tht.jobs.dwh_pipeline as module
|
import tht.jobs.dwh_pipeline as module
|
||||||
|
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ def _open_generation_fd(generations_fd: int, generation: str) -> int:
|
|||||||
|
|
||||||
|
|
||||||
def _active_generation_fd(
|
def _active_generation_fd(
|
||||||
root_fd: int, expected_binding: dict[str, str]
|
root_fd: int, expected_binding: dict[str, str], *, validate: bool = True
|
||||||
) -> tuple[str, int] | None:
|
) -> tuple[str, int] | None:
|
||||||
_validate_root_binding_fd(root_fd, expected_binding)
|
_validate_root_binding_fd(root_fd, expected_binding)
|
||||||
try:
|
try:
|
||||||
@@ -394,6 +394,7 @@ def _active_generation_fd(
|
|||||||
finally:
|
finally:
|
||||||
os.close(generations_fd)
|
os.close(generations_fd)
|
||||||
try:
|
try:
|
||||||
|
if validate:
|
||||||
validate_generation_fd(generation_fd, generation, expected_binding)
|
validate_generation_fd(generation_fd, generation, expected_binding)
|
||||||
return generation, generation_fd
|
return generation, generation_fd
|
||||||
except BaseException:
|
except BaseException:
|
||||||
@@ -408,10 +409,10 @@ def _materialize_generation_fd(
|
|||||||
_SNAPSHOT_DIRS.add(holder)
|
_SNAPSHOT_DIRS.add(holder)
|
||||||
snapshot_root = holder
|
snapshot_root = holder
|
||||||
try:
|
try:
|
||||||
manifest = validate_generation_fd(generation_fd, generation, binding)
|
_, payloads = _read_validated_generation_fd(generation_fd, generation, binding)
|
||||||
for name in manifest["files"]:
|
for name, payload in payloads.items():
|
||||||
destination = snapshot_root / name
|
destination = snapshot_root / name
|
||||||
destination.write_bytes(_read_owned_at(generation_fd, name, readonly=True))
|
destination.write_bytes(payload)
|
||||||
destination.chmod(0o400)
|
destination.chmod(0o400)
|
||||||
return holder, snapshot_root
|
return holder, snapshot_root
|
||||||
except BaseException:
|
except BaseException:
|
||||||
@@ -423,6 +424,15 @@ def _materialize_generation_fd(
|
|||||||
def validate_generation_fd(
|
def validate_generation_fd(
|
||||||
directory_fd: int, generation: str, expected_binding: dict[str, str] | None = None,
|
directory_fd: int, generation: str, expected_binding: dict[str, str] | None = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
manifest, _ = _read_validated_generation_fd(
|
||||||
|
directory_fd, generation, expected_binding
|
||||||
|
)
|
||||||
|
return manifest
|
||||||
|
|
||||||
|
|
||||||
|
def _read_validated_generation_fd(
|
||||||
|
directory_fd: int, generation: str, expected_binding: dict[str, str] | None = None,
|
||||||
|
) -> tuple[dict, dict[str, bytes]]:
|
||||||
try:
|
try:
|
||||||
directory_info = os.fstat(directory_fd)
|
directory_info = os.fstat(directory_fd)
|
||||||
if (
|
if (
|
||||||
@@ -453,13 +463,15 @@ def validate_generation_fd(
|
|||||||
)
|
)
|
||||||
if set(os.listdir(directory_fd)) != set(files) | {GENERATION_MANIFEST}:
|
if set(os.listdir(directory_fd)) != set(files) | {GENERATION_MANIFEST}:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
payloads = {}
|
||||||
for name, expected in files.items():
|
for name, expected in files.items():
|
||||||
if name != "physical.yaml" and not _SAFE_FILE.fullmatch(name):
|
if name != "physical.yaml" and not _SAFE_FILE.fullmatch(name):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
payload = _read_owned_at(directory_fd, name, readonly=True)
|
payload = _read_owned_at(directory_fd, name, readonly=True)
|
||||||
if hashlib.sha256(payload).hexdigest() != expected:
|
if hashlib.sha256(payload).hexdigest() != expected:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
return manifest
|
payloads[name] = payload
|
||||||
|
return manifest, payloads
|
||||||
except (OSError, KeyError, TypeError, ValueError, UnicodeDecodeError, json.JSONDecodeError) as error:
|
except (OSError, KeyError, TypeError, ValueError, UnicodeDecodeError, json.JSONDecodeError) as error:
|
||||||
raise CorruptCheckpointError("published DWH generation is invalid") from error
|
raise CorruptCheckpointError("published DWH generation is invalid") from error
|
||||||
|
|
||||||
@@ -478,7 +490,7 @@ def resolve_dwh_snapshot(cfg) -> DwhArtifactSnapshot:
|
|||||||
def _resolve_dwh_snapshot_locked(
|
def _resolve_dwh_snapshot_locked(
|
||||||
cfg, binding: dict[str, str], root_fd: int,
|
cfg, binding: dict[str, str], root_fd: int,
|
||||||
) -> DwhArtifactSnapshot:
|
) -> DwhArtifactSnapshot:
|
||||||
active = _active_generation_fd(root_fd, binding)
|
active = _active_generation_fd(root_fd, binding, validate=False)
|
||||||
if active is None:
|
if active is None:
|
||||||
return DwhArtifactSnapshot(
|
return DwhArtifactSnapshot(
|
||||||
None, cfg.paths.artifacts / "mschema" / "physical.yaml", cfg.paths.indexes / "lsh"
|
None, cfg.paths.artifacts / "mschema" / "physical.yaml", cfg.paths.indexes / "lsh"
|
||||||
@@ -576,7 +588,7 @@ class DwhPreprocessPipeline:
|
|||||||
_claim_or_validate_root_binding(lease.root_fd, self.binding)
|
_claim_or_validate_root_binding(lease.root_fd, self.binding)
|
||||||
lease.assert_root_identity(self.workspace_root / ".tht-dwh")
|
lease.assert_root_identity(self.workspace_root / ".tht-dwh")
|
||||||
self._assert_active_binding(lease.root_fd)
|
self._assert_active_binding(lease.root_fd)
|
||||||
active = _active_generation_fd(lease.root_fd, self.binding)
|
active = _active_generation_fd(lease.root_fd, self.binding, validate=False)
|
||||||
if active is not None:
|
if active is not None:
|
||||||
generation, generation_fd = active
|
generation, generation_fd = active
|
||||||
try:
|
try:
|
||||||
@@ -633,10 +645,22 @@ class DwhPreprocessPipeline:
|
|||||||
return self._publish_stage(context, "lsh", spec)
|
return self._publish_stage(context, "lsh", spec)
|
||||||
|
|
||||||
implementations = {"introspect": introspect_stage, "lsh": lsh_stage}
|
implementations = {"introspect": introspect_stage, "lsh": lsh_stage}
|
||||||
|
try:
|
||||||
return run_job(
|
return run_job(
|
||||||
spec, tuple(implementations[step] for step in steps),
|
spec, tuple(implementations[step] for step in steps),
|
||||||
reconcile_effects=self._reconcile_effects,
|
reconcile_effects=self._reconcile_effects,
|
||||||
)
|
)
|
||||||
|
finally:
|
||||||
|
holder, self._snapshot_holder = self._snapshot_holder, None
|
||||||
|
if isinstance(holder, Path):
|
||||||
|
shutil.rmtree(holder, ignore_errors=True)
|
||||||
|
_SNAPSHOT_DIRS.discard(holder)
|
||||||
|
if self.current_physical is not None and holder in self.current_physical.parents:
|
||||||
|
self.current_physical = None
|
||||||
|
if self.current_lsh_dir is not None and (
|
||||||
|
self.current_lsh_dir == holder or holder in self.current_lsh_dir.parents
|
||||||
|
):
|
||||||
|
self.current_lsh_dir = None
|
||||||
|
|
||||||
def _reconcile_effects(self, source, run_dir: Path) -> set[str]:
|
def _reconcile_effects(self, source, run_dir: Path) -> set[str]:
|
||||||
running = next(
|
running = next(
|
||||||
@@ -654,8 +678,11 @@ class DwhPreprocessPipeline:
|
|||||||
run_dir / "artifacts", running.artifact_files,
|
run_dir / "artifacts", running.artifact_files,
|
||||||
)
|
)
|
||||||
active = _active_generation_fd(lease.root_fd, self.binding)
|
active = _active_generation_fd(lease.root_fd, self.binding)
|
||||||
|
try:
|
||||||
if active is None or active[0] != source.run_id:
|
if active is None or active[0] != source.run_id:
|
||||||
raise CorruptCheckpointError("sealed DWH publication is not ACTIVE")
|
raise CorruptCheckpointError("sealed DWH publication is not ACTIVE")
|
||||||
|
finally:
|
||||||
|
if active is not None:
|
||||||
os.close(active[1])
|
os.close(active[1])
|
||||||
finally:
|
finally:
|
||||||
os.close(generations_fd)
|
os.close(generations_fd)
|
||||||
|
|||||||
Reference in New Issue
Block a user