coulomb-social/scripts/backup_capacity.py
tegwick 11cab7298b
Some checks failed
Use-case regression gate / usecase-regressions (push) Failing after 6m27s
Guard paired capture and mirror scratch capacity inside the writer lock
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a17f16-d99c-7ea3-95f0-8212897fba6b
2026-10-30 00:37:37 +01:00

160 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""Local backup capacity checks; no deletion, provider contact or app changes.
Candidate limits need operating admission. Estimates reserve complete pipeline
scratch, while RLIMIT_FSIZE bounds individual newly written files. This is not
a filesystem quota or a reservation against unrelated writers.
"""
from __future__ import annotations
import argparse
import json
import os
import resource
import shutil
import stat
from pathlib import Path
FILE_LIMIT = 1024**3
STATE_BUDGET = 8 * 1024**3
MINIMUM_FREE = 8 * 1024**3
CONTROL_MARGIN = 16 * 1024**2
MINIMUM_INODES = 1024
class Refused(ValueError):
pass
def active_file_limit():
return resource.getrlimit(resource.RLIMIT_FSIZE)[0]
def enforce_file_limit(maximum=FILE_LIMIT):
soft, hard = resource.getrlimit(resource.RLIMIT_FSIZE)
effective = min(
[maximum] + [value for value in [soft, hard] if value != resource.RLIM_INFINITY]
)
if effective <= 0:
raise Refused("positive_file_limit_required")
resource.setrlimit(resource.RLIMIT_FSIZE, (effective, effective))
return effective
def disk_free(path):
return shutil.disk_usage(path).free
def free_inodes(path):
return os.statvfs(path).f_favail
def state_usage(root):
info = root.lstat()
if (
not stat.S_ISDIR(info.st_mode)
or info.st_uid != os.geteuid()
or info.st_mode & 0o077
or root.resolve() != root.absolute()
):
raise Refused("private_state_directory_required")
def onerror(error):
raise error
used = max(info.st_size, info.st_blocks * 512)
for directory, directories, files in os.walk(root, followlinks=False, onerror=onerror):
for name in directories + files:
item = (Path(directory) / name).lstat()
if (
not (stat.S_ISREG(item.st_mode) or stat.S_ISDIR(item.st_mode))
or item.st_uid != os.geteuid()
or item.st_mode & 0o077
or (stat.S_ISREG(item.st_mode) and item.st_nlink != 1)
):
raise Refused("unsafe_state_entry")
used += max(item.st_size, item.st_blocks * 512)
return used
def reserve_plan(
state,
*,
phase="capture",
require_file_limit=True,
maximum_file=FILE_LIMIT,
state_budget=STATE_BUDGET,
minimum_free=MINIMUM_FREE,
):
if phase not in {"capture", "resume", "mirror"} or any(
type(v) is not int or v <= 0 for v in [maximum_file, state_budget, minimum_free]
):
raise Refused("explicit_positive_capacity_profile_required")
# New capture: dump, content, tar, ciphertext, readback, retained local copy,
# and temporary rewritten inventory. Sealed retry needs three new large
# files; mirror needs only its fresh ciphertext readback.
slots = {"capture": 7, "resume": 3, "mirror": 1}[phase]
state_reserve = slots * maximum_file + CONTROL_MARGIN
# The existing command wrapper uses one anonymous output sink outside state.
disk_reserve = state_reserve + maximum_file
used, available, inodes = state_usage(state), disk_free(state), free_inodes(state)
limit = active_file_limit()
bounded = limit != resource.RLIM_INFINITY and 0 < limit <= maximum_file
reasons = []
if used + state_reserve > state_budget:
reasons.append("local_retention_budget_required")
if available < minimum_free + disk_reserve:
reasons.append("disk_headroom_required")
if inodes < MINIMUM_INODES:
reasons.append("inode_headroom_required")
if require_file_limit and not bounded:
reasons.append("bounded_file_executor_required")
return {
"schema": "coulomb.backup-capacity.v1",
"phase": phase,
"status": "refused" if reasons else ("ready" if bounded else "reviewed_unbounded_executor"),
"reasons": reasons,
"state_bytes": used,
"state_budget_bytes": state_budget,
"state_reserve_bytes": state_reserve,
"disk_reserve_bytes": disk_reserve,
"free_bytes": available,
"minimum_free_bytes": minimum_free,
"free_inodes": inodes,
"minimum_free_inodes": MINIMUM_INODES,
"maximum_new_file_bytes": maximum_file,
"active_file_limit_bytes": limit,
"bounded_file_executor": bounded,
"state_modified": False,
"retained_data_deleted": False,
"provider_contacted": False,
"filesystem_blocks_reserved": False,
"limits_owner_admitted": False,
}
def require_capacity(state, *, phase="capture"):
report = reserve_plan(state, phase=phase)
if report["status"] != "ready":
raise Refused("backup_capacity_review_required")
return report
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--state-dir", type=Path, required=True)
parser.add_argument("--phase", choices=["capture", "resume", "mirror"], default="capture")
args = parser.parse_args()
print(
json.dumps(
reserve_plan(args.state_dir, phase=args.phase, require_file_limit=False), sort_keys=True
)
)
if __name__ == "__main__":
try:
main()
except (ValueError, OSError):
raise SystemExit("Capacity review refused; inspect private state locally.")