← Files Aholo Lux3DARCHIVED FILE

skills/lux3d/core/runtime/artifact_delivery.py

18.2 KB · Sep 30, 2026 · 23:15 UTC

↓ Download file

"""Validate Lux3D artifacts and build a secret-free delivery manifest."""

import argparse
import datetime
import hashlib
import json
import os
import pathlib
import re
import struct
import sys
import tempfile
import zipfile
import xml.etree.ElementTree as element_tree


SUPPORTED_FORMATS = {
    "zip",
    "glb",
    "ply",
    "usdz",
    "obj_zip",
    "fbx_zip",
    "stl",
    "3mf",
}
OPERATIONS = {
    "image-to-3d",
    "text-to-3d",
    "material-transfer",
    "four-view",
    "export",
    "query",
    "list",
}
SECRET_KEYS = {
    "apikey",
    "authorization",
    "password",
    "secret",
    "accesskey",
    "accesskeyid",
    "accesskeysecret",
    "accesstoken",
    "oustoken",
    "ststoken",
    "securitytoken",
}
TRANSIENT_URL_KEYS = {
    "img",
    "imgs",
    "meshurl",
    "modelurl",
    "outputs",
    "sourceurl",
    "sourceurltemporary",
    "imageurl",
    "fourviewurls",
}
URL_WITH_QUERY_PATTERN = re.compile(r"(https?://[^?\s\"'<>]+)\?[^\s\"'<>]+", re.IGNORECASE)


def utc_now():
    return datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat()


def sha256_file(path, chunk_size=1024 * 1024):
    digest = hashlib.sha256()
    with path.open("rb") as file_obj:
        while True:
            chunk = file_obj.read(chunk_size)
            if not chunk:
                break
            digest.update(chunk)
    return digest.hexdigest()


def normalize_secret_key(key):
    return re.sub(r"[^a-z0-9]", "", str(key).lower())


def assert_no_secrets(value, path="$"):
    if isinstance(value, dict):
        for key, child in value.items():
            if normalize_secret_key(key) in SECRET_KEYS:
                raise ValueError(f"Sensitive field is not allowed in delivery data: {path}.{key}")
            assert_no_secrets(child, f"{path}.{key}")
    elif isinstance(value, list):
        for index, child in enumerate(value):
            assert_no_secrets(child, f"{path}[{index}]")


def without_transient_urls(value):
    """Copy persistence data while dropping request and provider URL fields."""
    if isinstance(value, dict):
        return {
            key: without_transient_urls(child)
            for key, child in value.items()
            if normalize_secret_key(key) not in TRANSIENT_URL_KEYS
        }
    if isinstance(value, list):
        return [without_transient_urls(child) for child in value]
    if isinstance(value, str):
        return sanitize_error_message(value)
    return value


def sanitize_error_message(value):
    """Remove URL query strings before an exception is persisted."""
    if value is None:
        return None
    return URL_WITH_QUERY_PATTERN.sub(r"\1?<redacted>", str(value))


def infer_format(path):
    name = path.name.lower()
    if name.endswith(("_obj.zip", ".obj.zip", "obj.zip")):
        return "obj_zip"
    if name.endswith(("_fbx.zip", ".fbx.zip", "fbx.zip")):
        return "fbx_zip"
    if name.endswith(".usdz"):
        return "usdz"
    if name.endswith(".stl"):
        return "stl"
    if name.endswith(".3mf"):
        return "3mf"
    if name.endswith(".glb"):
        return "glb"
    if name.endswith(".ply"):
        return "ply"
    if name.endswith(".zip"):
        return "zip"
    raise ValueError(f"Cannot infer artifact format from filename: {path.name}")


def validate_path(file_path):
    path = pathlib.Path(file_path).expanduser().resolve()
    if not path.exists():
        raise FileNotFoundError(f"Artifact not found: {path}")
    if not path.is_file():
        raise ValueError(f"Artifact path is not a file: {path}")
    size = path.stat().st_size
    if size <= 0:
        raise ValueError(f"Artifact is empty: {path}")
    return path, size


def inspect_glb(path, size, require_embedded=False):
    with path.open("rb") as file_obj:
        header = file_obj.read(12)
        if len(header) != 12:
            raise ValueError("GLB header is truncated")
        magic, version, declared_length = struct.unpack("<4sII", header)
        if magic != b"glTF":
            raise ValueError("GLB magic must be glTF")
        if version != 2:
            raise ValueError(f"Unsupported GLB version: {version}")
        if declared_length != size:
            raise ValueError(
                f"GLB declared length {declared_length} does not match file size {size}"
            )
        chunk_header = file_obj.read(8)
        if len(chunk_header) != 8:
            raise ValueError("GLB JSON chunk header is missing")
        chunk_length, chunk_type = struct.unpack("<II", chunk_header)
        if chunk_type != 0x4E4F534A:
            raise ValueError("The first GLB chunk must be JSON")
        if 20 + chunk_length > size:
            raise ValueError("GLB JSON chunk exceeds the declared file length")
        json_bytes = file_obj.read(chunk_length)
        try:
            document = json.loads(json_bytes.rstrip(b" \t\r\n\x00").decode("utf-8"))
        except (UnicodeDecodeError, ValueError) as exc:
            raise ValueError("GLB JSON chunk is invalid") from exc
    if not isinstance(document, dict) or not isinstance(document.get("asset"), dict):
        raise ValueError("GLB JSON must be an object with an asset object")
    asset_version = document["asset"].get("version")
    if asset_version != "2.0":
        raise ValueError("GLB JSON asset.version must be 2.0")
    for field in ("scenes", "nodes", "meshes", "materials", "buffers", "images"):
        entries = document.get(field, [])
        if not isinstance(entries, list) or any(not isinstance(entry, dict) for entry in entries):
            raise ValueError(f"GLB {field} must be an array of objects")
    if require_embedded:
        for resource in document.get("buffers", []) + document.get("images", []):
            if "uri" in resource and (
                not isinstance(resource["uri"], str) or not resource["uri"].startswith("data:")
            ):
                raise ValueError("Offline GLB buffers and images must be embedded")
    return {
        "glbVersion": version,
        "assetVersion": asset_version,
        "scenes": len(document.get("scenes") or []),
        "nodes": len(document.get("nodes") or []),
        "meshes": len(document.get("meshes") or []),
        "materials": len(document.get("materials") or []),
    }


def _safe_zip_name(name):
    normalized = name.replace("\\", "/")
    if "\x00" in normalized:
        return False
    path = pathlib.PurePosixPath(normalized)
    if path.is_absolute() or ".." in path.parts:
        return False
    if path.parts and re.match(r"^[A-Za-z]:$", path.parts[0]):
        return False
    return True


def inspect_zip(path, output_format):
    if not zipfile.is_zipfile(path):
        raise ValueError(f"{output_format} artifact is not a valid ZIP container")
    with zipfile.ZipFile(path, "r") as archive:
        infos = [info for info in archive.infolist() if not info.is_dir()]
        if not infos:
            raise ValueError(f"{output_format} archive contains no files")
        unsafe = [info.filename for info in infos if not _safe_zip_name(info.filename)]
        if unsafe:
            raise ValueError(f"Archive contains unsafe path: {unsafe[0]}")
        broken = archive.testzip()
        if broken:
            raise ValueError(f"Archive CRC check failed for: {broken}")
        names = [info.filename for info in infos]
        lower_names = [name.lower() for name in names]
        required_suffixes = {
            "zip": (".glb",),
            "obj_zip": (".obj",),
            "fbx_zip": (".fbx",),
            "usdz": (".usd", ".usda", ".usdc"),
        }[output_format]
        model_entries = [
            name
            for name, lower in zip(names, lower_names)
            if lower.endswith(required_suffixes)
        ]
        if not model_entries:
            expected = ", ".join(required_suffixes)
            raise ValueError(
                f"{output_format} archive is missing a model entry ({expected})"
            )
        if output_format == "usdz":
            compressed = [
                info.filename for info in infos if info.compress_type != zipfile.ZIP_STORED
            ]
            if compressed:
                raise ValueError("USDZ entries must be stored without ZIP compression")
    return {
        "zipEntries": len(infos),
        "modelEntries": model_entries[:20],
    }


def inspect_ply(path):
    with path.open("rb") as file_obj:
        header_bytes = file_obj.read(1024 * 1024)
    marker = b"end_header"
    marker_index = header_bytes.find(marker)
    if marker_index < 0:
        raise ValueError("PLY end_header was not found in the first MiB")
    header_slice = header_bytes[: marker_index + len(marker)]
    try:
        header = header_slice.decode("ascii")
    except UnicodeDecodeError as exc:
        raise ValueError("PLY header must be ASCII") from exc
    lines = [line.strip() for line in header.replace("\r", "").split("\n")]
    if not lines or lines[0] != "ply":
        raise ValueError("PLY header must start with ply")
    format_lines = [line for line in lines if line.startswith("format ")]
    if len(format_lines) != 1:
        raise ValueError("PLY header must contain exactly one format declaration")
    parts = format_lines[0].split()
    if len(parts) != 3 or parts[1] not in {
        "ascii",
        "binary_little_endian",
        "binary_big_endian",
    }:
        raise ValueError("PLY format declaration is invalid")
    vertex_lines = [line for line in lines if line.startswith("element vertex ")]
    if len(vertex_lines) != 1:
        raise ValueError("PLY header must contain one vertex element")
    try:
        vertices = int(vertex_lines[0].split()[2])
    except (IndexError, ValueError) as exc:
        raise ValueError("PLY vertex count is invalid") from exc
    if vertices <= 0:
        raise ValueError("PLY must contain at least one vertex")
    return {"plyFormat": parts[1], "vertices": vertices}


def inspect_stl(path, size):
    """Perform bounded structural checks for binary or ASCII STL."""
    with path.open("rb") as file_obj:
        binary_header = file_obj.read(84)
    if len(binary_header) == 84:
        triangles = struct.unpack("<I", binary_header[80:84])[0]
        expected_size = 84 + triangles * 50
        if triangles > 0 and expected_size == size:
            return {"stlEncoding": "binary", "triangles": triangles}

    facets = 0
    saw_end = False
    with path.open("rb") as file_obj:
        first_line = file_obj.readline(4096).strip().lower()
        if not first_line.startswith(b"solid"):
            raise ValueError("STL is neither a complete binary STL nor an ASCII solid")
        for raw_line in file_obj:
            line = raw_line.strip().lower()
            if line.startswith(b"facet normal"):
                facets += 1
            elif line.startswith(b"endsolid"):
                saw_end = True
    if facets <= 0 or not saw_end:
        raise ValueError("ASCII STL must contain at least one facet and endsolid")
    return {"stlEncoding": "ascii", "triangles": facets}


def inspect_3mf(path):
    """Check the required ZIP package parts and model XML of a 3MF file."""
    if not zipfile.is_zipfile(path):
        raise ValueError("3mf artifact is not a valid ZIP container")
    with zipfile.ZipFile(path, "r") as archive:
        infos = [info for info in archive.infolist() if not info.is_dir()]
        unsafe = [info.filename for info in infos if not _safe_zip_name(info.filename)]
        if unsafe:
            raise ValueError(f"Archive contains unsafe path: {unsafe[0]}")
        broken = archive.testzip()
        if broken:
            raise ValueError(f"Archive CRC check failed for: {broken}")
        names = {info.filename.replace("\\", "/").lower(): info.filename for info in infos}
        if "[content_types].xml" not in names:
            raise ValueError("3mf package is missing [Content_Types].xml")
        model_names = [
            original
            for lower, original in names.items()
            if lower.startswith("3d/") and lower.endswith(".model")
        ]
        if not model_names:
            raise ValueError("3mf package is missing a 3D model part")
        try:
            root = element_tree.fromstring(archive.read(model_names[0]))
        except element_tree.ParseError as exc:
            raise ValueError("3mf model XML is invalid") from exc
        if root.tag.rsplit("}", 1)[-1].lower() != "model":
            raise ValueError("3mf model part must have a model root element")
    return {"zipEntries": len(infos), "modelPart": model_names[0]}


def inspect_artifact(file_path, format_hint=None, *, require_embedded_glb=False):
    path, size = validate_path(file_path)
    output_format = format_hint or infer_format(path)
    if output_format not in SUPPORTED_FORMATS:
        raise ValueError(f"Unsupported artifact format: {output_format}")
    details = {}
    if output_format == "glb":
        details = inspect_glb(path, size, require_embedded=require_embedded_glb)
    elif output_format in {"zip", "usdz", "obj_zip", "fbx_zip"}:
        details = inspect_zip(path, output_format)
    elif output_format == "ply":
        details = inspect_ply(path)
    elif output_format == "stl":
        details = inspect_stl(path, size)
    elif output_format == "3mf":
        details = inspect_3mf(path)
    return {
        "path": str(path),
        "format": output_format,
        "size": size,
        "sha256": sha256_file(path),
        "validation": {"valid": True, **details},
    }


def integration_guidance(target_kind, formats):
    preferred = {
        "web": ["glb"],
        "unity": ["glb", "fbx_zip"],
        "unreal": ["fbx_zip", "glb"],
        "blender": ["glb", "fbx_zip", "obj_zip"],
        "dcc": ["glb", "fbx_zip", "obj_zip"],
        "apple-ar": ["usdz"],
        "editable-pbr": ["zip"],
        "geometry": ["ply", "stl"],
        "manufacturing": ["3mf", "stl"],
        "generic": ["glb", "zip"],
    }.get(target_kind, ["glb", "zip"])
    available_preferred = [item for item in preferred if item in formats]
    return {
        "targetKind": target_kind,
        "preferredAvailableFormats": available_preferred,
        "requiresTargetProjectVerification": True,
        "note": "Inspect and follow the target project's existing asset and loader conventions before integration.",
    }


def build_manifest(spec):
    if not isinstance(spec, dict):
        raise ValueError("Delivery spec must be a JSON object")
    assert_no_secrets(spec)
    spec = without_transient_urls(spec)
    operation = spec.get("operation")
    if operation not in OPERATIONS:
        raise ValueError("Delivery spec has an unsupported operation")
    region = spec.get("region")
    if region not in {"cn", "international"}:
        raise ValueError("Delivery spec region must be cn or international")
    task_id = spec.get("taskId")
    if task_id in (None, ""):
        raise ValueError("Delivery spec taskId is required")
    step_id = spec.get("stepId")
    attempt_id = spec.get("attemptId")
    if not isinstance(step_id, str) or not step_id:
        raise ValueError("Delivery spec stepId is required")
    if not isinstance(attempt_id, str) or not attempt_id:
        raise ValueError("Delivery spec attemptId is required")
    target = spec.get("target")
    if not isinstance(target, dict) or not str(target.get("kind") or "").strip():
        raise ValueError("Delivery spec target.kind is required")
    artifacts = spec.get("artifacts")
    if not isinstance(artifacts, list) or not artifacts:
        raise ValueError("Delivery spec artifacts must be a non-empty list")
    inspected = []
    for index, artifact in enumerate(artifacts):
        if not isinstance(artifact, dict) or not artifact.get("path"):
            raise ValueError(f"artifacts[{index}].path is required")
        item = inspect_artifact(artifact["path"], artifact.get("format"))
        item["artifactId"] = artifact.get(
            "artifactId", f"{step_id}:{attempt_id}:{index + 1}"
        )
        item["sourceStepId"] = step_id
        item["version"] = artifact.get("version", attempt_id)
        inspected.append(item)
    formats = [item["format"] for item in inspected]
    return {
        "schema": "lux3d.delivery/v2",
        "createdAt": utc_now(),
        "workflow": {
            "operation": operation,
            "region": region,
            "version": spec.get("version"),
            "taskId": str(task_id),
            "stepId": step_id,
            "attemptId": attempt_id,
        },
        "inputs": spec.get("inputs") or {},
        "artifacts": inspected,
        "target": target,
        "integration": integration_guidance(str(target["kind"]), formats),
    }


def write_json_atomic(path, payload):
    target = pathlib.Path(path).expanduser().resolve()
    target.parent.mkdir(parents=True, exist_ok=True)
    handle, temporary = tempfile.mkstemp(
        prefix=f".{target.name}.", suffix=".tmp", dir=str(target.parent)
    )
    try:
        with os.fdopen(handle, "w", encoding="utf-8") as file_obj:
            json.dump(payload, file_obj, ensure_ascii=False, indent=2)
            file_obj.write("\n")
        os.replace(temporary, target)
    except Exception:
        if os.path.exists(temporary):
            os.remove(temporary)
        raise


def build_parser():
    parser = argparse.ArgumentParser(description="Validate and deliver Lux3D artifacts.")
    subparsers = parser.add_subparsers(dest="command")
    inspect_parser = subparsers.add_parser("inspect")
    inspect_parser.add_argument("file_path")
    inspect_parser.add_argument("--format", choices=sorted(SUPPORTED_FORMATS))
    manifest = subparsers.add_parser("manifest")
    manifest.add_argument("--spec", required=True)
    manifest.add_argument("--output", required=True)
    return parser


def main():
    parser = build_parser()
    args = parser.parse_args()
    if args.command == "inspect":
        result = inspect_artifact(args.file_path, args.format)
    elif args.command == "manifest":
        with open(args.spec, "r", encoding="utf-8") as file_obj:
            spec = json.load(file_obj)
        result = build_manifest(spec)
        write_json_atomic(args.output, result)
    else:
        parser.print_help()
        raise SystemExit(1)
    print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    try:
        main()
    except (FileNotFoundError, ValueError, RuntimeError, TimeoutError) as exc:
        print(f"[ERROR] {exc}", file=sys.stderr)
        raise SystemExit(1)

SHA-256: 3d92a710fcfb9bcc63373798225049198108b9378b019c25d0f0f6236ee2cfe3