feat(observatory): add portable calculation profiles
This commit is contained in:
@@ -0,0 +1,588 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build and verify a deterministic blocked M4.9 executor release candidate.
|
||||
|
||||
This builder deliberately cannot mark the executor ready. It seals the exact
|
||||
portable source materializer, generic TRAVEL/TGS runner, result-v2 assembler
|
||||
and validator into a reproducible candidate archive. A later installation
|
||||
step must additionally provide an exact compiled runner, an executor image and
|
||||
an installation receipt before the runtime registry can become ready.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import gzip
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import subprocess
|
||||
import tarfile
|
||||
import tempfile
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path, PurePosixPath
|
||||
from typing import Final, cast
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
M49_EXECUTOR_RELEASE_CANDIDATE_SCHEMA: Final = (
|
||||
"missioncore.m49-tgs-portable-executor-release-candidate/v1"
|
||||
)
|
||||
M49_COMPILED_RUNNER_BUILD_SCHEMA: Final = "missioncore.m49-tgs-portable-compiled-runner-build/v1"
|
||||
M49_EXECUTOR_RELEASE_ID: Final = "m49-tgs-portable-executor-v1"
|
||||
M49_TRAVEL_IMAGE_SHA256: Final = "7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f"
|
||||
M49_PROFILE_SHA256: Final = "6128d6af7e6137f9a9473db045e3b155e2105319159f17c32f344b4aedf823a9"
|
||||
M49_RESULT_CONTRACT_SHA256: Final = (
|
||||
"9dd80c8e2504559d2156fca933de6eb27901e35305e6853aeb84707e1cb13892"
|
||||
)
|
||||
M49_RUNNER_SOURCE_SHA256: Final = "52813392aabd02efc5c2b8f7c22ed88e3ef4cc8ad3aafeba2792efe503e29fe9"
|
||||
M49_RUNNER_WRAPPER_SHA256: Final = (
|
||||
"2d6c32560682647f868e4ce4c2605749f17c60482a609f8c03ff951411f48ffb"
|
||||
)
|
||||
M49_COMPILER_CONTRACT: Final = {
|
||||
"compiler": "g++",
|
||||
"language_standard": "c++17",
|
||||
"flags": ["-O3", "-DNDEBUG", "-pthread"],
|
||||
"travel_include": "/opt/travel/src/TRAVEL/cpp/travel/core",
|
||||
"eigen_include": "/usr/include/eigen3",
|
||||
}
|
||||
M49_RELEASE_BLOCKERS: Final = (
|
||||
"compiled-runner-artifact-missing",
|
||||
"exact-executor-image-missing",
|
||||
"worker-installation-receipt-missing",
|
||||
)
|
||||
M49_RELEASE_SOURCES: Final = (
|
||||
Path("config/perception/m49-tgs-portable-v2.json"),
|
||||
Path("experiments/perception/worker/observatory_portable/Dockerfile.m49-portable-executor"),
|
||||
Path(
|
||||
"experiments/perception/worker/observatory_portable/"
|
||||
"Invoke-M49PortableExecutorCandidateInstall.ps1"
|
||||
),
|
||||
Path("experiments/perception/worker/observatory_portable/m49-tgs-portable-runner-source.json"),
|
||||
Path("experiments/perception/worker/observatory_portable/run_m49_tgs_portable.cpp"),
|
||||
Path("experiments/perception/worker/observatory_portable/run_m49_tgs_portable.sh"),
|
||||
Path("experiments/perception/worker/observatory_portable/smoke_m49_tgs_portable.sh"),
|
||||
Path("experiments/perception/worker/m49_t3_travel/build_tgs_fail_closed_evidence.py"),
|
||||
Path("experiments/perception/worker/m49_t3_travel/build_tgs_full_shadow_binary.sh"),
|
||||
Path("experiments/perception/worker/m49_t3_travel/build_tgs_full_shadow_evidence.py"),
|
||||
Path("src/k1link/compute/lidar_replay.py"),
|
||||
Path("src/k1link/observatory/m49_portable_executor.py"),
|
||||
Path("src/k1link/observatory/m49_portable_result.py"),
|
||||
Path("src/k1link/observatory/m49_portable_source.py"),
|
||||
Path("src/k1link/observatory/portable_result_contract.py"),
|
||||
)
|
||||
_AUTHORITY: Final = {
|
||||
"commands_enabled": False,
|
||||
"actuation_allowed": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
"production_accepted": False,
|
||||
}
|
||||
_REVISION: Final = re.compile(r"^[a-f0-9]{40}$")
|
||||
_SHA256: Final = re.compile(r"^[a-f0-9]{64}$")
|
||||
_HASH_CHUNK_BYTES: Final = 1024 * 1024
|
||||
|
||||
|
||||
class M49ExecutorReleaseBuildError(RuntimeError):
|
||||
"""The exact portable executor candidate cannot be built or verified."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class BuiltM49ExecutorReleaseCandidate:
|
||||
archive: Path
|
||||
archive_sha256: str
|
||||
candidate_sha256: str
|
||||
manifest: dict[str, object]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class BuiltM49CompiledRunnerSeal:
|
||||
binary: Path
|
||||
manifest_path: Path
|
||||
manifest_sha256: str
|
||||
manifest: dict[str, object]
|
||||
|
||||
|
||||
def seal_m49_compiled_runner_build(
|
||||
*,
|
||||
source_root: Path,
|
||||
source_revision: str,
|
||||
binary_path: Path,
|
||||
manifest_path: Path,
|
||||
) -> BuiltM49CompiledRunnerSeal:
|
||||
"""Seal, but never execute, a binary built in the exact TRAVEL image."""
|
||||
|
||||
if _REVISION.fullmatch(source_revision) is None:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 compiled-runner revision is invalid")
|
||||
root = source_root.expanduser().resolve(strict=True)
|
||||
if root.is_symlink() or not root.is_dir():
|
||||
raise M49ExecutorReleaseBuildError("M4.9 compiled-runner source root is unsafe")
|
||||
source = root / "experiments/perception/worker/observatory_portable/run_m49_tgs_portable.cpp"
|
||||
wrapper = root / "experiments/perception/worker/observatory_portable/run_m49_tgs_portable.sh"
|
||||
profile = root / "config/perception/m49-tgs-portable-v2.json"
|
||||
if (
|
||||
_sha256_file(source) != M49_RUNNER_SOURCE_SHA256
|
||||
or _sha256_file(wrapper) != M49_RUNNER_WRAPPER_SHA256
|
||||
or _sha256_file(profile) != M49_PROFILE_SHA256
|
||||
):
|
||||
raise M49ExecutorReleaseBuildError(
|
||||
"M4.9 compiled-runner sources differ from the exact release"
|
||||
)
|
||||
binary = binary_path.expanduser().absolute()
|
||||
try:
|
||||
metadata = binary.lstat()
|
||||
resolved_binary = binary.resolve(strict=True)
|
||||
except OSError as exc:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 compiled runner is unavailable") from exc
|
||||
if (
|
||||
stat.S_ISLNK(metadata.st_mode)
|
||||
or not stat.S_ISREG(metadata.st_mode)
|
||||
or not os.path.samefile(binary, resolved_binary)
|
||||
or not os.access(resolved_binary, os.X_OK)
|
||||
or not _is_elf(resolved_binary)
|
||||
):
|
||||
raise M49ExecutorReleaseBuildError("M4.9 compiled runner is not an executable ELF artifact")
|
||||
manifest: dict[str, object] = {
|
||||
"schema_version": M49_COMPILED_RUNNER_BUILD_SCHEMA,
|
||||
"source_revision": source_revision,
|
||||
"source_state": "committed-snapshot",
|
||||
"build_image_sha256": M49_TRAVEL_IMAGE_SHA256,
|
||||
"profile_sha256": M49_PROFILE_SHA256,
|
||||
"runner_source_sha256": M49_RUNNER_SOURCE_SHA256,
|
||||
"runner_wrapper_sha256": M49_RUNNER_WRAPPER_SHA256,
|
||||
"compiler_contract": dict(M49_COMPILER_CONTRACT),
|
||||
"binary": {
|
||||
"file_name": "run_m49_tgs_portable",
|
||||
"format": "elf",
|
||||
"byte_length": resolved_binary.stat().st_size,
|
||||
"sha256": _sha256_file(resolved_binary),
|
||||
},
|
||||
"authority": dict(_AUTHORITY),
|
||||
}
|
||||
payload = _canonical_json(manifest)
|
||||
target = manifest_path.expanduser().absolute()
|
||||
if target.exists():
|
||||
raise M49ExecutorReleaseBuildError("M4.9 compiled-runner seal already exists")
|
||||
target.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
if target.parent.is_symlink() or not target.parent.is_dir():
|
||||
raise M49ExecutorReleaseBuildError("M4.9 compiled-runner seal root is unsafe")
|
||||
target.write_bytes(payload)
|
||||
return BuiltM49CompiledRunnerSeal(
|
||||
binary=resolved_binary,
|
||||
manifest_path=target,
|
||||
manifest_sha256=hashlib.sha256(payload).hexdigest(),
|
||||
manifest=manifest,
|
||||
)
|
||||
|
||||
|
||||
def git_revision(repository_root: Path = REPOSITORY_ROOT) -> str:
|
||||
completed = subprocess.run(
|
||||
["git", "rev-parse", "HEAD"],
|
||||
cwd=repository_root,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
revision = completed.stdout.strip()
|
||||
if completed.returncode != 0 or _REVISION.fullmatch(revision) is None:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release revision is unavailable")
|
||||
return revision
|
||||
|
||||
|
||||
def materialize_revision(
|
||||
*, revision: str, destination: Path, repository_root: Path = REPOSITORY_ROOT
|
||||
) -> None:
|
||||
"""Extract only the explicit release source set from one exact commit."""
|
||||
|
||||
if _REVISION.fullmatch(revision) is None or destination.exists():
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release revision request is invalid")
|
||||
verified = subprocess.run(
|
||||
["git", "rev-parse", "--verify", f"{revision}^{{commit}}"],
|
||||
cwd=repository_root,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if verified.returncode != 0 or verified.stdout.strip() != revision:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release revision is not a commit")
|
||||
archive_path = destination.parent / "source.tar"
|
||||
archived = subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"archive",
|
||||
"--format=tar",
|
||||
"--output",
|
||||
str(archive_path),
|
||||
revision,
|
||||
"--",
|
||||
*(path.as_posix() for path in M49_RELEASE_SOURCES),
|
||||
],
|
||||
cwd=repository_root,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if archived.returncode != 0:
|
||||
raise M49ExecutorReleaseBuildError(
|
||||
"M4.9 release sources are not all present in the selected commit"
|
||||
)
|
||||
destination.mkdir()
|
||||
resolved_root = destination.resolve()
|
||||
with tarfile.open(archive_path, "r:") as archive:
|
||||
members = archive.getmembers()
|
||||
for member in members:
|
||||
target = (destination / member.name).resolve()
|
||||
if (
|
||||
target != resolved_root
|
||||
and resolved_root not in target.parents
|
||||
or not (member.isdir() or member.isreg())
|
||||
):
|
||||
raise M49ExecutorReleaseBuildError("M4.9 Git archive contains an unsafe member")
|
||||
for member in members:
|
||||
target = destination / member.name
|
||||
if member.isdir():
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
continue
|
||||
source = archive.extractfile(member)
|
||||
if source is None:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 Git archive member is unreadable")
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
with source, target.open("wb") as output:
|
||||
while chunk := source.read(_HASH_CHUNK_BYTES):
|
||||
output.write(chunk)
|
||||
|
||||
|
||||
def build_m49_executor_release_candidate(
|
||||
*,
|
||||
source_root: Path,
|
||||
output_directory: Path,
|
||||
source_revision: str,
|
||||
source_state: str,
|
||||
) -> BuiltM49ExecutorReleaseCandidate:
|
||||
"""Build a deterministic candidate; never a ready installation artifact."""
|
||||
|
||||
if _REVISION.fullmatch(source_revision) is None:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 source revision is invalid")
|
||||
if source_state not in {"committed-snapshot", "uncommitted-candidate"}:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 source state is invalid")
|
||||
root = source_root.expanduser().resolve(strict=True)
|
||||
if root.is_symlink() or not root.is_dir():
|
||||
raise M49ExecutorReleaseBuildError("M4.9 source root is unsafe")
|
||||
files = _source_inventory(root)
|
||||
identity: dict[str, object] = {
|
||||
"schema_version": M49_EXECUTOR_RELEASE_CANDIDATE_SCHEMA,
|
||||
"release_id": M49_EXECUTOR_RELEASE_ID,
|
||||
"state": "blocked",
|
||||
"source_revision": source_revision,
|
||||
"source_state": source_state,
|
||||
"worker_contour_id": "worker-006",
|
||||
"travel_build_image_sha256": M49_TRAVEL_IMAGE_SHA256,
|
||||
"executor_image_sha256": None,
|
||||
"compiled_runner": None,
|
||||
"profile_sha256": M49_PROFILE_SHA256,
|
||||
"result_contract_sha256": M49_RESULT_CONTRACT_SHA256,
|
||||
"source_contract": {
|
||||
"camera": "exact-admitted-fmp4-members",
|
||||
"spatial_replay": "exact-admitted-k1mqtt-member",
|
||||
"host_time_metadata": "separate-exact-admitted-member-required",
|
||||
"server_paths_or_commands_allowed": False,
|
||||
},
|
||||
"phases": [
|
||||
{"phase_id": "source-materializer", "state": "implemented"},
|
||||
{"phase_id": "travel-tgs-runner", "state": "implemented"},
|
||||
{"phase_id": "result-v2-assembler", "state": "implemented"},
|
||||
{"phase_id": "exact-result-validator", "state": "implemented"},
|
||||
{"phase_id": "package-sealer", "state": "implemented"},
|
||||
],
|
||||
"files": files,
|
||||
"blockers": list(M49_RELEASE_BLOCKERS),
|
||||
"authority": dict(_AUTHORITY),
|
||||
}
|
||||
candidate_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
manifest: dict[str, object] = {
|
||||
**identity,
|
||||
"candidate_sha256": candidate_sha256,
|
||||
}
|
||||
output = output_directory.expanduser().absolute()
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
if output.is_symlink() or not output.is_dir():
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release output is unsafe")
|
||||
target = output / f"m49-tgs-portable-executor-{candidate_sha256}.tgz"
|
||||
if target.exists():
|
||||
verified = verify_m49_executor_release_candidate(target)
|
||||
if verified.candidate_sha256 != candidate_sha256:
|
||||
raise M49ExecutorReleaseBuildError(
|
||||
"existing M4.9 release candidate has another identity"
|
||||
)
|
||||
return verified
|
||||
with tempfile.TemporaryDirectory(prefix="m49-executor-candidate-") as temporary:
|
||||
stage = Path(temporary)
|
||||
payload = stage / "payload"
|
||||
for source in M49_RELEASE_SOURCES:
|
||||
destination = payload / source
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
destination.write_bytes((root / source).read_bytes())
|
||||
(stage / "release-manifest.json").write_bytes(_canonical_json(manifest))
|
||||
_write_archive(stage, target)
|
||||
verified = verify_m49_executor_release_candidate(target)
|
||||
if verified.candidate_sha256 != candidate_sha256:
|
||||
raise M49ExecutorReleaseBuildError("built M4.9 release candidate changed identity")
|
||||
return verified
|
||||
|
||||
|
||||
def verify_m49_executor_release_candidate(
|
||||
archive_path: Path,
|
||||
) -> BuiltM49ExecutorReleaseCandidate:
|
||||
candidate = archive_path.expanduser().resolve(strict=True)
|
||||
if candidate.is_symlink() or not candidate.is_file():
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release archive is unsafe")
|
||||
with tarfile.open(candidate, "r:gz") as archive:
|
||||
members = archive.getmembers()
|
||||
names = [member.name for member in members]
|
||||
payload_names: set[str] = set()
|
||||
for source in M49_RELEASE_SOURCES:
|
||||
parts = PurePosixPath("payload", *source.parts)
|
||||
for parent in reversed(parts.parents):
|
||||
name = parent.as_posix()
|
||||
if name not in {".", "payload"}:
|
||||
payload_names.add(name)
|
||||
payload_names.add(parts.as_posix())
|
||||
expected_names = [
|
||||
"release-manifest.json",
|
||||
"payload",
|
||||
*sorted(payload_names),
|
||||
]
|
||||
if names != expected_names:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release archive member set changed")
|
||||
by_name = {member.name: member for member in members}
|
||||
for member in members:
|
||||
path = PurePosixPath(member.name)
|
||||
if (
|
||||
path.is_absolute()
|
||||
or any(part in {"", ".", ".."} for part in path.parts)
|
||||
or not (member.isdir() or member.isreg())
|
||||
or member.uid != 0
|
||||
or member.gid != 0
|
||||
or member.mtime != 0
|
||||
):
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release archive metadata is unsafe")
|
||||
manifest_stream = archive.extractfile(by_name["release-manifest.json"])
|
||||
if manifest_stream is None:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release manifest is unavailable")
|
||||
manifest_payload = manifest_stream.read()
|
||||
try:
|
||||
manifest = _object(json.loads(manifest_payload), "M4.9 release manifest")
|
||||
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release manifest is invalid JSON") from exc
|
||||
if manifest_payload != _canonical_json(manifest):
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release manifest is not canonical JSON")
|
||||
candidate_sha256 = cast(str, manifest.pop("candidate_sha256", None))
|
||||
if (
|
||||
_SHA256.fullmatch(candidate_sha256 or "") is None
|
||||
or manifest.get("schema_version") != M49_EXECUTOR_RELEASE_CANDIDATE_SCHEMA
|
||||
or manifest.get("state") != "blocked"
|
||||
or tuple(cast(list[object], manifest.get("blockers"))) != M49_RELEASE_BLOCKERS
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or hashlib.sha256(_canonical_json(manifest)).hexdigest() != candidate_sha256
|
||||
):
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release candidate identity changed")
|
||||
files = cast(list[object], manifest.get("files"))
|
||||
expected_files = {path.as_posix() for path in M49_RELEASE_SOURCES}
|
||||
if {cast(dict[str, object], row).get("relative_path") for row in files} != expected_files:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release file set changed")
|
||||
for value in files:
|
||||
row = _object(value, "M4.9 release file")
|
||||
relative = cast(str, row["relative_path"])
|
||||
member = by_name[f"payload/{relative}"]
|
||||
stream = archive.extractfile(member)
|
||||
if stream is None:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release file is unavailable")
|
||||
payload = stream.read()
|
||||
if (
|
||||
row.get("byte_length") != len(payload)
|
||||
or row.get("sha256") != hashlib.sha256(payload).hexdigest()
|
||||
):
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release file changed")
|
||||
restored_manifest = {**manifest, "candidate_sha256": candidate_sha256}
|
||||
return BuiltM49ExecutorReleaseCandidate(
|
||||
archive=candidate,
|
||||
archive_sha256=_sha256_file(candidate),
|
||||
candidate_sha256=candidate_sha256,
|
||||
manifest=restored_manifest,
|
||||
)
|
||||
|
||||
|
||||
def write_worktree_candidate_manifest(
|
||||
*, source_root: Path, target: Path, source_revision: str
|
||||
) -> dict[str, object]:
|
||||
"""Write a reviewable manifest without pretending it is a Git release."""
|
||||
|
||||
files = _source_inventory(source_root.expanduser().resolve(strict=True))
|
||||
identity: dict[str, object] = {
|
||||
"schema_version": M49_EXECUTOR_RELEASE_CANDIDATE_SCHEMA,
|
||||
"release_id": M49_EXECUTOR_RELEASE_ID,
|
||||
"state": "blocked",
|
||||
"source_revision": source_revision,
|
||||
"source_state": "uncommitted-candidate",
|
||||
"worker_contour_id": "worker-006",
|
||||
"travel_build_image_sha256": M49_TRAVEL_IMAGE_SHA256,
|
||||
"executor_image_sha256": None,
|
||||
"compiled_runner": None,
|
||||
"profile_sha256": M49_PROFILE_SHA256,
|
||||
"result_contract_sha256": M49_RESULT_CONTRACT_SHA256,
|
||||
"source_contract": {
|
||||
"camera": "exact-admitted-fmp4-members",
|
||||
"spatial_replay": "exact-admitted-k1mqtt-member",
|
||||
"host_time_metadata": "separate-exact-admitted-member-required",
|
||||
"server_paths_or_commands_allowed": False,
|
||||
},
|
||||
"phases": [
|
||||
{"phase_id": "source-materializer", "state": "implemented"},
|
||||
{"phase_id": "travel-tgs-runner", "state": "implemented"},
|
||||
{"phase_id": "result-v2-assembler", "state": "implemented"},
|
||||
{"phase_id": "exact-result-validator", "state": "implemented"},
|
||||
{"phase_id": "package-sealer", "state": "implemented"},
|
||||
],
|
||||
"files": files,
|
||||
"blockers": [
|
||||
*M49_RELEASE_BLOCKERS,
|
||||
"committed-source-snapshot-missing",
|
||||
],
|
||||
"authority": dict(_AUTHORITY),
|
||||
}
|
||||
manifest: dict[str, object] = {
|
||||
**identity,
|
||||
"candidate_sha256": hashlib.sha256(_canonical_json(identity)).hexdigest(),
|
||||
}
|
||||
destination = target.expanduser().absolute()
|
||||
if destination.exists():
|
||||
raise M49ExecutorReleaseBuildError("worktree candidate manifest already exists")
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
destination.write_bytes(_canonical_json(manifest))
|
||||
return manifest
|
||||
|
||||
|
||||
def _source_inventory(root: Path) -> list[dict[str, object]]:
|
||||
rows: list[dict[str, object]] = []
|
||||
for relative in M49_RELEASE_SOURCES:
|
||||
path = root / relative
|
||||
try:
|
||||
resolved = path.resolve(strict=True)
|
||||
except OSError as exc:
|
||||
raise M49ExecutorReleaseBuildError(
|
||||
f"M4.9 release source is unavailable: {relative}"
|
||||
) from exc
|
||||
if path.is_symlink() or not resolved.is_file() or not resolved.is_relative_to(root):
|
||||
raise M49ExecutorReleaseBuildError(f"M4.9 release source is unsafe: {relative}")
|
||||
rows.append(
|
||||
{
|
||||
"relative_path": relative.as_posix(),
|
||||
"byte_length": resolved.stat().st_size,
|
||||
"sha256": _sha256_file(resolved),
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
def _write_archive(stage: Path, target: Path) -> None:
|
||||
members = [stage / "release-manifest.json", stage / "payload"]
|
||||
members.extend(sorted((stage / "payload").rglob("*")))
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
with (
|
||||
target.open("wb") as raw,
|
||||
gzip.GzipFile(filename="", mode="wb", fileobj=raw, mtime=0) as compressed,
|
||||
tarfile.open(fileobj=compressed, mode="w", format=tarfile.PAX_FORMAT) as archive,
|
||||
):
|
||||
for path in members:
|
||||
relative = path.relative_to(stage).as_posix()
|
||||
info = tarfile.TarInfo(relative)
|
||||
info.uid = 0
|
||||
info.gid = 0
|
||||
info.uname = "root"
|
||||
info.gname = "root"
|
||||
info.mtime = 0
|
||||
if path.is_dir():
|
||||
info.type = tarfile.DIRTYPE
|
||||
info.mode = 0o755
|
||||
archive.addfile(info, io.BytesIO())
|
||||
else:
|
||||
info.type = tarfile.REGTYPE
|
||||
info.mode = 0o755 if path.suffix in {".sh", ".py"} else 0o644
|
||||
info.size = path.stat().st_size
|
||||
with path.open("rb") as stream:
|
||||
archive.addfile(info, stream)
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
try:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode("utf-8")
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise M49ExecutorReleaseBuildError("M4.9 release manifest is not JSON-compatible") from exc
|
||||
|
||||
|
||||
def _sha256_file(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(_HASH_CHUNK_BYTES), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _is_elf(path: Path) -> bool:
|
||||
try:
|
||||
with path.open("rb") as stream:
|
||||
return stream.read(4) == b"\x7fELF"
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def _object(value: object, label: str) -> dict[str, object]:
|
||||
if not isinstance(value, dict) or any(not isinstance(key, str) for key in value):
|
||||
raise M49ExecutorReleaseBuildError(f"{label} must be an object")
|
||||
return cast(dict[str, object], value)
|
||||
|
||||
|
||||
def _parse_arguments(arguments: Sequence[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--output-directory", type=Path, required=True)
|
||||
parser.add_argument("--revision")
|
||||
return parser.parse_args(arguments)
|
||||
|
||||
|
||||
def main(arguments: Sequence[str] | None = None) -> int:
|
||||
options = _parse_arguments(arguments)
|
||||
revision = options.revision or git_revision()
|
||||
with tempfile.TemporaryDirectory(prefix="m49-revision-") as temporary:
|
||||
snapshot = Path(temporary) / "source"
|
||||
materialize_revision(revision=revision, destination=snapshot)
|
||||
built = build_m49_executor_release_candidate(
|
||||
source_root=snapshot,
|
||||
output_directory=options.output_directory,
|
||||
source_revision=revision,
|
||||
source_state="committed-snapshot",
|
||||
)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"ok": True,
|
||||
"state": "blocked",
|
||||
"artifact": str(built.archive),
|
||||
"sha256": built.archive_sha256,
|
||||
"candidate_sha256": built.candidate_sha256,
|
||||
"blockers": list(M49_RELEASE_BLOCKERS),
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Print the immutable Mac launchd plan for the Worker 006 reverse tunnel."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from k1link.observatory.worker_tunnel_launchd import (
|
||||
plan_observatory_worker_tunnel_launch_agent,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--data-directory", type=Path, required=True)
|
||||
parser.add_argument(
|
||||
"--agent-path",
|
||||
type=Path,
|
||||
default=(
|
||||
Path.home()
|
||||
/ "Library/LaunchAgents/com.nodedc.observatory-worker-tunnel.local.plist"
|
||||
),
|
||||
)
|
||||
parser.add_argument("--ssh-path", type=Path, default=Path("/usr/bin/ssh"))
|
||||
arguments = parser.parse_args()
|
||||
plan = plan_observatory_worker_tunnel_launch_agent(
|
||||
data_directory=arguments.data_directory,
|
||||
agent_path=arguments.agent_path,
|
||||
ssh_path=arguments.ssh_path,
|
||||
)
|
||||
print(json.dumps(plan.to_dict(), indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user