feat: prepare pinned Custodian worker bundle and required input admission

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a15374-f59f-7a31-86d2-d23b5d2ed181
This commit is contained in:
tegwick 2026-10-19 15:09:27 +02:00
parent 0319fe8be8
commit fed119a17c
18 changed files with 1058 additions and 2 deletions

View file

@ -0,0 +1,81 @@
"""Fail-closed admission for trusted-owner fleet snapshots and freshness receipts."""
import hashlib
import json
import stat
from datetime import UTC, datetime
from pathlib import Path, PurePosixPath
def _read(path):
path = Path(path)
if any(p.is_symlink() for p in [path, *path.parents]) or not stat.S_ISREG(path.stat().st_mode) or path.stat().st_size > 8 * 1024 * 1024:
raise ValueError("unsafe fleet evidence path or size")
return path.read_bytes()
def admit(fleet, policy_path, receipts_path, repos, now=None):
"""Files/receipts are protected owner inputs, not signatures or remote verification."""
fleet = Path(fleet)
policy = json.loads(_read(policy_path))
if policy.get("schema") != "activity-core.custodian-input-policy.v1" or set(policy["repositories"]) != set(repos):
raise ValueError("fleet policy repository mismatch")
raw = _read(fleet / "manifest.json")
if _read(fleet / "COMPLETE").decode().strip() != hashlib.sha256(raw).hexdigest():
raise ValueError("incomplete fleet snapshot")
manifest = json.loads(raw)
records = manifest.get("repositories", [])
if manifest.get("schema") != "repo-manager.fleet-input.v1" or len(records) != len(repos) or {r["slug"] for r in records} != set(repos):
raise ValueError("fleet snapshot repository mismatch")
if {p.name for p in fleet.iterdir()} != set(repos) | {"manifest.json", "COMPLETE"}:
raise ValueError("snapshot contains unexpected repository or file")
receipts = json.loads(_read(receipts_path))
now = now or datetime.now(UTC)
expected_files = set()
total = 0
for record in records:
valid = False
for receipt in receipts:
if receipt.get("schema") != "repo-manager.freshness-receipt.v1" or receipt.get("repo_slug") != record["slug"] or receipt.get("forge_commit") != record["commit"]:
continue
hub = receipt.get("hub", {})
if hub.get("verified_primary") is not True or hub.get("instance_role") != "primary" or hub.get("instance_label") != policy["expected_hub_instance"]:
continue
if receipt.get("outcome") not in {"applied", "noop"}:
continue
noop = receipt.get("outcome") == "noop" and receipt.get("reason") == "forge_head_matches_last_success"
bound = all(receipt.get(key) == record["commit"] for key in ("expected_commit", "derived_commit", "projected_commit"))
# Native issuer emits this reason only after HEAD == last successful commit.
# Receipts are protected owner evidence; this is not signature verification.
bound = bound or noop
if not bound:
continue
observed = datetime.fromisoformat(receipt["finished_at"])
if observed.tzinfo is None:
continue
age = (now - observed).total_seconds()
if -policy["max_future_skew_seconds"] <= age <= policy["max_observation_age_seconds"]:
valid = True
if not valid:
raise ValueError("missing accepted fresh Forge receipt")
for entry in record["files"]:
relative = PurePosixPath(entry["path"])
if relative.is_absolute() or ".." in relative.parts or relative.parts[0] not in {"workplans", "decisions"} or relative.suffix != ".md":
raise ValueError("unsafe snapshot record")
path = fleet / record["slug"] / relative
data = _read(path)
total += len(data)
if total > policy["max_snapshot_bytes"]:
raise ValueError("snapshot size policy exceeded")
if len(data) != entry["bytes"] or hashlib.sha256(data).hexdigest() != entry["sha256"]:
raise ValueError("snapshot record hash mismatch")
expected_files.add(path)
actual_files = set()
for slug in repos:
for path in (fleet / slug).rglob("*"):
if path.is_symlink():
raise ValueError("snapshot symlink refused")
if path.is_file():
actual_files.add(path)
if actual_files != expected_files or total > policy["max_snapshot_bytes"]:
raise ValueError("snapshot content outside manifest or size policy")
return hashlib.sha256(raw).hexdigest()

View file

@ -89,6 +89,10 @@ def build_command(operation: str, params: dict) -> tuple[list[str], Path, Path,
raise ValueError("explicit canonical security-flow repository list required")
for repo in repos:
_path(str(fleet / repo / "workplans"), directory=True)
from activity_core.custodian_inputs import admit
policy_path = _path(os.environ.get("ACTIVITY_CORE_CUSTODIAN_INPUT_POLICY"))
receipts_path = _path(os.environ.get("ACTIVITY_CORE_CUSTODIAN_FRESHNESS_RECEIPTS"))
admit(fleet, policy_path, receipts_path, repos)
hub = os.environ.get("ACTIVITY_CORE_CUSTODIAN_HUB", "")
parsed = urlsplit(hub)
if parsed.scheme not in {"http", "https"} or not parsed.hostname or parsed.username or parsed.password or parsed.query or parsed.fragment or parsed.path not in {"", "/"}: