"""Allowlisted consistent HF packages. Never uploads; never packages arbitrary cwd files.""" from __future__ import annotations import argparse import hashlib import json import re import shutil import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[1] DIRS = ("backend", "frontend", "gpu_service", "docs", "deploy", "scripts", "tests") FILES = ("README.md", "Dockerfile", ".dockerignore", ".gitignore", ".env.example", "pyproject.toml", "requirements.txt", "requirements-dev.txt", "requirements-gpu.txt", "constraints-cpu.txt", "model-lock.json") EXCLUDED = {"node_modules", "dist", "__pycache__", ".pytest_cache", ".ruff_cache", ".venv", "output", "private-media"} ALLOWED = {".py", ".md", ".txt", ".json", ".html", ".css", ".ts", ".tsx", ".js", ".sh", ".ttf", ".toml", ".Dockerfile"} REQUIRED = ("backend/main.py", "backend/session.py", "backend/perception.py", "backend/portfolio.py", "backend/public_output.py", "backend/settings.py", "backend/schemas.py", "frontend/package.json", "frontend/package-lock.json", "frontend/index.html", "frontend/vite.config.ts", "frontend/src/App.tsx", "frontend/src/api.ts", "frontend/src/styles.css", "frontend/src/mediaCapture.ts", "frontend/src/transport.ts", "frontend/src/audio.ts", "frontend/src/latency.ts", "frontend/public/audio-worklet.js", "frontend/src/components/ObservatoryLogo.tsx", "frontend/src/components/InstrumentPicker.tsx", "frontend/src/instruments.ts", "frontend/src/components/PortfolioEditor.tsx", "frontend/src/portfolio.ts", "frontend/src/instrumentSearch.ts", "frontend/src/catalog/equities.json", "frontend/src/catalog/funds.json", "frontend/src/catalog/maritime.json", "frontend/src/assets/manrope/Manrope-Variable.ttf", "frontend/src/assets/manrope/OFL.txt", "frontend/public/licenses/Manrope-OFL.txt", "gpu_service/app.py", "gpu_service/native.py", "gpu_service/contracts.py", "gpu_service/smoke.py", "requirements.txt", "requirements-gpu.txt", "constraints-cpu.txt", "deploy/gpu.Dockerfile", "docs/model-interface.md", "docs/architecture.md", "docs/hugging-face-delivery.md", "docs/verification.md", "model-lock.json", "scripts/verify_gpu_install.py") SECRET = re.compile(rb"\b(?:hf_[A-Za-z0-9]{16,}|sk-[A-Za-z0-9_-]{20,})\b") def files(): result = [] for directory in DIRS: for path in (ROOT / directory).rglob("*"): relative = path.relative_to(ROOT) if set(relative.parts) & EXCLUDED or path.name.endswith(".tsbuildinfo"): continue if path.is_symlink(): raise RuntimeError("Symlink is not allowed in release: " + str(relative)) if path.is_dir(): continue if path.name.startswith(".env"): raise RuntimeError("Runtime environment file cannot be released: " + str(relative)) if path.suffix not in ALLOWED and path.name != "Dockerfile": raise RuntimeError("Unexpected file in release directory: " + str(relative)) result.append(path) result.extend(ROOT / name for name in FILES) for relative in REQUIRED: if ROOT / relative not in result: raise RuntimeError("Required application file missing: " + relative) return sorted(set(result)) def main(): parser = argparse.ArgumentParser() parser.add_argument("--validate-only", action="store_true") parser.add_argument("--output", type=Path, default=ROOT / "release") args = parser.parse_args() selected = files() manifest = {} for source in selected: data = source.read_bytes() # Secret-shaped fixture rejection strings are written via concatenation # in tests, so the exact package never contains usable token-shaped text. if SECRET.search(data): raise RuntimeError("Credential-shaped value found; remove it before release: " + str(source.relative_to(ROOT))) manifest[str(source.relative_to(ROOT))] = hashlib.sha256(data).hexdigest() canonical = json.dumps(manifest, sort_keys=True, separators=(",", ":")).encode() revision = hashlib.sha256(canonical).hexdigest() try: git_revision = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, stderr=subprocess.DEVNULL, text=True).strip() except subprocess.CalledProcessError: git_revision = None if args.validate_only: print(f"Package validation passed: {len(manifest)} allowlisted files; source SHA256 {revision}") return destination = args.output.resolve() # Keep all writes inside an explicit package output folder; refuse workspace root. if destination == ROOT or ROOT.is_relative_to(destination): raise RuntimeError("Choose a release output subdirectory, not the source root") for flavor in ("app", "gpu"): stage = destination / flavor if stage.exists(): shutil.rmtree(stage) stage.mkdir(parents=True) for source in selected: target = stage / source.relative_to(ROOT) target.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(source, target) if hashlib.sha256(target.read_bytes()).hexdigest() != manifest[str(source.relative_to(ROOT))]: raise RuntimeError("Source changed during packaging; rerun after edits finish") if flavor == "gpu": shutil.copyfile(stage / "deploy/gpu.Dockerfile", stage / "Dockerfile") (stage / "README.md").write_text("---\ntitle: OmnAI MiniCPM-o 4.5\nsdk: docker\napp_port: 8090\n---\n\nPersistent authenticated MiniCPM-o 4.5 audiovisual and portfolio research service. One model, one ongoing native duplex session. See docs/model-interface.md, docs/architecture.md and docs/hugging-face-delivery.md.\n\nSource package SHA256: " + revision + "\n") package_files = {str(p.relative_to(stage)): hashlib.sha256(p.read_bytes()).hexdigest() for p in stage.rglob("*") if p.is_file()} record = {"source_sha256": revision, "git_revision": git_revision, "flavor": flavor, "source_files": manifest, "package_files": package_files} (stage / "RELEASE_MANIFEST.json").write_text(json.dumps(record, indent=2) + "\n") for name, digest in package_files.items(): assert hashlib.sha256((stage / name).read_bytes()).hexdigest() == digest print(f"Validated app and GPU packages: {destination}\nShared source SHA256: {revision}") if __name__ == "__main__": main()