Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1abf1-7e95-7141-87c9-ee78332bf790
97 lines
5.1 KiB
Python
97 lines
5.1 KiB
Python
"""Contained owner retention for locally verified Vergabe provider points only."""
|
|
|
|
import base64
|
|
import json
|
|
import os
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
import vergabe_pilot_point as point
|
|
import vergabe_pilot_publish as publisher
|
|
from vergabe_pilot_recovery_store import Refused, require
|
|
|
|
BASELINE_SHA = "4915288384e3ba8a87f1427b8e10106ee7241c04184adb97f439e57846bef76f"
|
|
BASELINE_BYTES = 182521
|
|
|
|
|
|
class Objects:
|
|
def __init__(self, helper, auth):
|
|
self.root = publisher.HOST + "/remote.php/dav/files/Backup/railiance-backups/" + publisher.PREFIX + "/"
|
|
self.headers = {"Authorization": "Basic " + base64.b64encode((auth[0] + ":" + auth[1]).encode()).decode()}
|
|
self.opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), helper.NoRedirect())
|
|
|
|
def request(self, item, delete=False, etag=None):
|
|
point.run_time(item["run_id"])
|
|
url = self.root + "vergabe-pilot-" + item["run_id"] + ".zip.age"
|
|
headers = dict(self.headers)
|
|
if delete:
|
|
require(isinstance(etag, str) and etag.startswith('"') and etag.endswith('"')
|
|
and len(etag) < 1024 and not any(c in etag for c in "\r\n"), "strong_provider_etag_required")
|
|
headers["If-Match"] = etag
|
|
try:
|
|
with self.opener.open(urllib.request.Request(url, method="DELETE" if delete else "GET", headers=headers), timeout=60) as response:
|
|
content = response.read(1024 if delete else item["ciphertext_bytes"] + 1)
|
|
return response.status, content, response.headers.get("ETag")
|
|
except urllib.error.HTTPError as error:
|
|
return error.code, b"", None
|
|
|
|
|
|
def execute(plan, objects, removed):
|
|
import hashlib
|
|
require(0 < len(plan["keep"]) <= 10 and len(plan["candidates"]) <= 50, "retention_batch_bound")
|
|
baseline = [p for p in plan["keep"] if p["run_id"] == point.BASELINE]
|
|
require(len(baseline) == 1 and baseline[0]["ciphertext_sha256"] == BASELINE_SHA
|
|
and baseline[0]["ciphertext_bytes"] == BASELINE_BYTES, "verified_baseline_required")
|
|
require(not ({p["run_id"] for p in plan["keep"]} & {p["run_id"] for p in plan["candidates"]}),
|
|
"retention_sets_overlap")
|
|
etags = {}
|
|
# Independently recheck every retained point before allowing any deletion.
|
|
for item in [*plan["keep"], *plan["candidates"]]:
|
|
code, content, etag = objects.request(item)
|
|
require(code == 200 and len(content) == item["ciphertext_bytes"]
|
|
and hashlib.sha256(content).hexdigest() == item["ciphertext_sha256"], "retention_provider_bytes_changed")
|
|
require(isinstance(etag, str) and etag.startswith('"') and etag.endswith('"')
|
|
and not etag.startswith('W/') and not any(c in etag for c in "\r\n"), "provider_etag_required")
|
|
etags[item["run_id"]] = etag
|
|
deleted = []
|
|
for item in plan["candidates"]:
|
|
require(item["run_id"] != point.BASELINE, "baseline_delete_refused")
|
|
code, _, _ = objects.request(item, delete=True, etag=etags[item["run_id"]])
|
|
require(code == 204, "conditional_retention_delete_failed")
|
|
require(objects.request(item)[0] == 404, "retention_delete_readback_failed")
|
|
# Durable per-point tombstones make partial batches safely resumable.
|
|
removed(item)
|
|
deleted.append(item["run_id"])
|
|
return deleted
|
|
|
|
|
|
def child(root, output, inventory):
|
|
fd = os.open(output, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
|
|
receipt = {"schema": "railiance.vergabe-pilot-provider-retention.v1", "status": "failed",
|
|
"deleted": [], "application_changed": False, "local_copies_deleted": False}
|
|
try:
|
|
helper = publisher.owner_helpers()
|
|
publisher.require_owner_handoff()
|
|
credentials = publisher.custody(helper)
|
|
plan = point.retention_plan(inventory(root))
|
|
receipt["retained"] = [p["run_id"] for p in plan["keep"]]
|
|
receipt["planned_candidates"] = [p["run_id"] for p in plan["candidates"]]
|
|
receipt["quota_before"] = publisher.Provider(helper).quota(credentials["owner_auth"])
|
|
def removed(item):
|
|
path = root / item["run_id"] / "provider-removed.json"
|
|
marker = {**item, "status": "provider_removed_and_readback_absent"}
|
|
with os.fdopen(os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600), "w") as stream:
|
|
json.dump(marker, stream)
|
|
stream.flush()
|
|
os.fsync(stream.fileno())
|
|
receipt["deleted"].append(item["run_id"])
|
|
execute(plan, Objects(helper, credentials["owner_auth"]), removed)
|
|
receipt["status"] = "provider_retention_complete"
|
|
except BaseException as error: # noqa: BLE001 — preserve partial progress without private diagnostics
|
|
receipt["failure"] = str(error) if isinstance(error, Refused) else "provider_retention_failed"
|
|
finally:
|
|
with os.fdopen(fd, "w") as stream:
|
|
json.dump(receipt, stream, indent=2)
|
|
stream.flush()
|
|
os.fsync(stream.fileno())
|
|
return 0 if receipt["status"] == "provider_retention_complete" else 1
|