mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-30 20:35:22 +08:00
159 lines
6.5 KiB
Python
159 lines
6.5 KiB
Python
"""Verify an already-built LocalAgent archive. Never rebuild or sign it.
|
|
|
|
Real SDK artifact/dependency stores and direct OSS dependency installer; this
|
|
is NOT shared-worker nsjail or live Core/model acceptance. All packaged tests
|
|
run from the read-only extraction in a fresh process.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import base64
|
|
import hashlib
|
|
import importlib.metadata
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
from langbot_plugin.cli.utils.page_components import discover_plugin_components, populate_plugin_pages
|
|
from langbot_plugin.entities.io.context import PluginWorkerPolicy
|
|
from langbot_plugin.runtime.plugin.artifact import PluginArtifactStore
|
|
from langbot_plugin.runtime.plugin.dependency_environment import PluginDependencyEnvironmentStore
|
|
from langbot_plugin.runtime.plugin.worker_launcher import PluginWorkerLauncher
|
|
from langbot_plugin.utils.discover.engine import ComponentDiscoveryEngine
|
|
|
|
|
|
def sha(data):
|
|
return hashlib.sha256(data).hexdigest()
|
|
|
|
|
|
def verify_sdk():
|
|
dist = importlib.metadata.distribution("langbot-plugin")
|
|
from inspect import signature
|
|
|
|
from langbot_plugin.api.entities.builtin.runner.context import RunnerContext
|
|
|
|
assert "paths" in signature(RunnerContext.export_box_files).parameters
|
|
checked = {}
|
|
for entry in dist.files or []:
|
|
if entry.hash is None:
|
|
continue
|
|
data = dist.locate_file(entry).read_bytes()
|
|
digest = hashlib.new(entry.hash.mode, data).digest()
|
|
assert base64.urlsafe_b64encode(digest).decode().rstrip("=") == entry.hash.value, str(entry)
|
|
checked[str(entry)] = sha(data)
|
|
assert checked
|
|
return {"version": dist.version, "record_checked_files": checked, "python": sys.version}
|
|
|
|
|
|
async def verify(args):
|
|
source = args.source.resolve()
|
|
evidence = args.evidence.resolve()
|
|
evidence.mkdir(parents=True, exist_ok=False)
|
|
package = args.package.resolve()
|
|
raw = package.read_bytes()
|
|
digest = sha(raw)
|
|
|
|
def git(*arguments):
|
|
return subprocess.check_output(["git", "-C", str(source), *arguments])
|
|
|
|
commit = git("rev-parse", "HEAD").decode().strip()
|
|
assert not git("status", "--porcelain", "--", "."), "Candidate source must be clean"
|
|
prefix = git("rev-parse", "--show-prefix").decode().strip()
|
|
tracked = git("ls-tree", "-r", "--name-only", "HEAD", "--", ".").decode().splitlines()
|
|
entries = {}
|
|
with zipfile.ZipFile(package) as archive:
|
|
assert len(archive.namelist()) == len(set(archive.namelist()))
|
|
# Compare every payload byte with the commit; SDK only rewrites manifest.
|
|
assert set(archive.namelist()) == set(tracked)
|
|
for name in tracked:
|
|
payload = archive.read(name)
|
|
if name != "manifest.yaml":
|
|
assert payload == git("show", f"HEAD:{prefix}{name}"), name
|
|
entries[name] = sha(payload)
|
|
os.chdir(source)
|
|
discovery = ComponentDiscoveryEngine()
|
|
manifest = discovery.load_component_manifest("manifest.yaml", owner="builtin", no_save=True)
|
|
populate_plugin_pages(manifest, discover_plugin_components(manifest, discovery))
|
|
generated = yaml.safe_dump(manifest.manifest, allow_unicode=True, sort_keys=False).encode()
|
|
assert archive.read("manifest.yaml") == generated
|
|
assert manifest.manifest["execution"]["sharedRuntime"] == "shared-runtime-v1"
|
|
sdk = verify_sdk()
|
|
artifact_store = PluginArtifactStore(evidence / "runtime")
|
|
artifact = artifact_store.install_package(raw, digest)
|
|
assert artifact_store.require_verified(digest) == artifact
|
|
store = PluginDependencyEnvironmentStore(evidence / "runtime")
|
|
launcher = PluginWorkerLauncher()
|
|
launcher.configure(
|
|
PluginWorkerPolicy(max_cpus=1, max_memory_mb=512, max_pids=32, max_open_files=128, max_file_size_mb=32),
|
|
"oss_dev",
|
|
)
|
|
ready = await launcher.prepare_dependency_environment(store, artifact)
|
|
assert store.get_ready(ready.digest) == ready
|
|
assert await launcher.prepare_dependency_environment(store, artifact) == ready
|
|
assert not (ready.site_packages_path / "langbot_plugin").exists()
|
|
assert (ready.site_packages_path / "pydantic").is_dir()
|
|
(evidence / "sdk-record.json").write_text(json.dumps(sdk, indent=2) + "\n")
|
|
env = {
|
|
**os.environ,
|
|
"PYTHONDONTWRITEBYTECODE": "1",
|
|
"PYTHONPATH": os.pathsep.join(
|
|
(str(launcher.runtime_import_root), str(ready.site_packages_path), str(artifact.code_path))
|
|
),
|
|
}
|
|
commands = [
|
|
sys.executable,
|
|
"-m",
|
|
"pytest",
|
|
"tests",
|
|
"-q",
|
|
"-p",
|
|
"no:cacheprovider",
|
|
"--junitxml=" + str(evidence / "tests.xml"),
|
|
]
|
|
result = subprocess.run(
|
|
commands,
|
|
cwd=artifact.code_path,
|
|
env=env,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
timeout=180,
|
|
)
|
|
(evidence / "packaged-tests.log").write_text(result.stdout)
|
|
report = {
|
|
"source_commit": commit,
|
|
"package": str(package),
|
|
"sha256": digest,
|
|
"size": len(raw),
|
|
"entry_sha256": entries,
|
|
"artifact_code": str(artifact.code_path),
|
|
"sdk_version": sdk["version"],
|
|
"sdk_record_checked_count": len(sdk["record_checked_files"]),
|
|
"dependency_environment": str(ready.root_path),
|
|
"dependency_environment_digest": ready.digest,
|
|
"dependency_admission": True,
|
|
"cache_readback": True,
|
|
"sdk_shadow_installed": False,
|
|
"packaged_tests_exit": result.returncode,
|
|
"command": commands,
|
|
"limitations": "Direct OSS dependency installer and one in-process real SDK component over loopback RPC; simulated Host/model/storage/credentials/Box only. No supervisor/nsjail, live Core DB, signer, upstream model, or live Workspace admission.",
|
|
}
|
|
(evidence / "inventory.json").write_text(json.dumps(report, indent=2) + "\n")
|
|
print(json.dumps({k: v for k, v in report.items() if k != "entry_sha256"}, indent=2))
|
|
assert result.returncode == 0, result.stdout
|
|
assert sha(package.read_bytes()) == digest
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--package", type=Path, required=True)
|
|
parser.add_argument("--evidence", type=Path, required=True, help="New directory, must not already exist")
|
|
parser.add_argument("--source", type=Path, default=Path(__file__).resolve().parents[1] / "LocalAgent")
|
|
asyncio.run(verify(parser.parse_args()))
|