openclaw/scripts/mantis/read-request-archive.py
Martin Cleary a960844a9c
feat(qa): run isolated behavioral proof for inline reviews (#138953)
* feat(telegram): add isolated Test Server proof workflow

Add maintainer-only exact-head admission, durable at-most-once QA lease consumption, isolated candidate execution, and normalized trusted Telegram Test Server observations.

Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com>

* feat(proof): bind named Web UI and canonical Telegram QA evidence

Reuse the existing formatting QA recipe, preserve isolated exact-candidate execution and produce the consumer request-bound receipt. Keep named smoke scenarios distinct and protect stationary harness ancestry.

Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com>

* fix(proof): complete isolated QA execution and bounded failure capture

Reuse canonical ephemeral device pairing and QA RPC scopes, preserve strict startup probes and recorder locks, and retain bounded wrong-text attempts without Telegram delivery.

Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com>

* fix(mantis): revoke proof forwarding and enforce lease roles

* fix(mantis): close proof producer CI gates

* test(mantis): consolidate related proof suites within CI budget

* test(mantis): preserve fast QA ownership when grouping integration cases

* fix(mantis): accept exact branch-qualified workflow paths

* fix(mantis): align live admission workflow path checks

* feat(mantis): collect selected proof inside the originating review

* fix(mantis): bound proof storage and preserve failure evidence

Reuse verified storage across request-bound candidates, reserve backing capacity, retain sanitized rejection evidence, and repair cleanup and observation finalization. Scoped checks and dirty review pass; full Gateway and sandboxed browser runtime proof remain required before publication.

* fix(mantis): retain bridge identity before startup

* fix(mantis): keep candidate config readable under private umask

* fix(qa): use verified rootless networking and join candidate shutdown

* fix(qa): repair proof tooling checks and deterministic recorder fixture

---------

Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com>
2026-09-07 14:09:37 +01:00

95 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""Validate bounded archive entries before exporting regular proof/bundle files."""
import base64
import json
import os
from pathlib import Path, PurePosixPath
import stat
import sys
import tarfile
import zipfile
mode, source, destination = sys.argv[1:]
limit = 16 * 1024 * 1024
seen = set()
total = 0
def admit(name, size):
global total
if not name or len(name) > 240 or "\\" in name or "\x00" in name or ":" in name:
raise ValueError("unsafe archive path")
parts = name.rstrip("/").split("/")
if any(part in ("", ".", "..") for part in parts) or PurePosixPath(name).is_absolute():
raise ValueError("unsafe archive path")
if name in seen or len(seen) >= 4096 or size < 0 or size > limit:
raise ValueError("duplicate or oversized archive entry")
seen.add(name)
total += size
if total > (256 * 1024 * 1024 if mode == "bundle" else limit):
raise ValueError("oversized archive")
if mode == "bundle":
root = Path(destination)
root.mkdir(mode=0o700)
with tarfile.open(source, "r:") as archive:
for entry in archive:
name = entry.name
# docker cp wraps the selected directory in one root component.
if name.startswith("./"):
name = name[2:]
if name in (".", "control-ui", "control-ui/") and entry.isdir():
continue
if name.startswith("control-ui/"):
name = name[len("control-ui/"):]
admit(name, entry.size)
if entry.isdir():
(root / name).mkdir(parents=True, exist_ok=True)
elif entry.isfile():
file = root / name
file.parent.mkdir(parents=True, exist_ok=True)
data = archive.extractfile(entry).read(limit + 1)
if len(data) != entry.size:
raise ValueError("truncated bundle entry")
with file.open("xb") as output:
output.write(data)
else:
raise ValueError("bundle links and special files are forbidden")
if not (root / "index.html").is_file():
raise ValueError("bundle has no index.html")
# Exported public UI assets must be readable by the non-root observer,
# independent of the host umask. They are mounted read-only afterward.
for directory, _, names in os.walk(root):
os.chmod(directory, 0o755)
for name in names:
os.chmod(Path(directory) / name, 0o444)
elif mode in ("evidence", "telegram-evidence", "telegram-qa-evidence"):
required = ({"qa-execution.json", "qa-result.json", "qa-observations.json"}
if mode == "telegram-qa-evidence" else
{"telegram-send.json", "provider-request.json", "telegram-reply.json"}
if mode == "telegram-evidence" else
{"observer.json", "chat-send.json", "final-reply.json", "final-reply.png"})
files = {}
diagnostic = {"telegram-failure.json"} if mode == "telegram-evidence" else set()
with zipfile.ZipFile(source) as archive:
for entry in archive.infolist():
name = entry.filename
if entry.orig_filename != name:
raise ValueError("noncanonical archive path")
admit(name, entry.file_size)
kind = stat.S_IFMT(entry.external_attr >> 16)
if name not in required | diagnostic or kind not in (0, stat.S_IFREG) or entry.is_dir() or entry.flag_bits & 1:
raise ValueError("unexpected or unsafe evidence entry")
if name.endswith(".json") and entry.file_size > 65536:
raise ValueError("oversized evidence JSON")
if name in diagnostic and entry.file_size > 16384:
raise ValueError("oversized failure diagnostic")
with archive.open(entry) as stream:
data = stream.read(limit + 1)
if len(data) != entry.file_size:
raise ValueError("truncated evidence entry")
files[name] = base64.b64encode(data).decode("ascii")
if set(files) != required and (not diagnostic or set(files) != diagnostic):
raise ValueError("incomplete evidence inventory")
Path(destination).write_text(json.dumps(files), encoding="utf-8")
else:
raise ValueError("unknown archive mode")