release: freeze Nexus One AI 1.0.0-rc1
This commit is contained in:
1 parent
63acefd890
commit
4f1419ca93
152 files changed
+9445
-828
No files matched your search
@@ -1,8 +1,9 @@
|
||||
# Nexus One AI Offline Server Payload
|
||||
# Nexus One AI Offline Workstation Payload
|
||||
|
||||
This directory defines the payload embedded by `build-iso-offline.sh`. The
|
||||
offline installer is deliberately separate from the existing online bootstrap
|
||||
ISO until disconnected clean-install validation is complete.
|
||||
This directory contains Gate 2 Offline Workstation payload-resolution and disposable-validation
|
||||
tools. **It does not currently contain a lock-driven bundle assembler or `build-iso-offline.sh`.**
|
||||
No command in this directory produces a release ISO, and the existing mutable body in
|
||||
`build-bundle.sh` is deliberately unreachable.
|
||||
|
||||
The agreed bootable release matrix is:
|
||||
|
||||
@@ -30,15 +31,76 @@ updates are periodically rolled into refreshed ISO builds.
|
||||
Server L/Max models are intentionally separate signed model packs. Optional external
|
||||
connectors cannot operate without a customer-approved route to their target.
|
||||
|
||||
## Build stages
|
||||
## Current operator flow
|
||||
|
||||
1. On a connected Ubuntu 22.04 staging host with at least 80 GiB free, run
|
||||
`sudo bash autoinstall/offline/build-bundle.sh`.
|
||||
2. Review `autoinstall/offline-output/manifest/` and sign
|
||||
`SHA256SUMS` with the release key.
|
||||
3. Run `sudo bash autoinstall/build-iso-offline.sh` to inject the verified
|
||||
payload into the Ubuntu Server ISO.
|
||||
4. Validate with all network interfaces disconnected.
|
||||
The only authorized/current flow is:
|
||||
|
||||
The bundle builder fails closed when an expected payload is absent. An ISO must
|
||||
not be labelled offline merely because the source tree was embedded.
|
||||
1. Resolve candidate inventory on a connected, isolated staging host.
|
||||
2. Review candidate object identity, bytes, origin, license and unresolved entries.
|
||||
3. Validate the candidate lock; it MUST fail while status is not `reviewed` or unresolved entries exist.
|
||||
4. Exercise signed-profile, checkpoint, install and rollback components only against disposable targets.
|
||||
5. Stop. Assembly, ISO injection, signing and release validation are not implemented.
|
||||
|
||||
| Stage | Tool | Current status |
|
||||
|---|---|---|
|
||||
| Source/OCI candidate resolution | `resolve-payload-lock.py` | Evidence-only; OCI archive bytes/licenses remain open |
|
||||
| Clean APT closure | `resolve-apt-lock.py` | Evidence-only; repository snapshot/origin/licenses and NVIDIA APT closure remain open |
|
||||
| Backend/Chroma/platform wheel closures | `resolve-python-lock.py` | Evidence-only; three independent environments, candidate lock only |
|
||||
| Whole-lock preflight | `validate-payload-lock.py` | Available and fail-closed |
|
||||
| CPU/NVIDIA profile bytes | `validate-python-runtime-profile.py` | Available for exact candidate directories |
|
||||
| Signed membership | `verify-python-profile-membership.py` | Implemented/test-key proven; production trust inputs absent |
|
||||
| Selection planning | `plan-python-runtime-profile.py` | Non-mutating; production receipt required by default |
|
||||
| Atomic journal/checkpoint preparation | `prepare-python-profile-operation.py` | Disposable/pre-mutation evidence only |
|
||||
| Offline Python mutation/rollback | `install-python-profile.py` | Disposable targets only; not installer-wired |
|
||||
| Bundle assembly | `build-bundle.sh` | Hard blocked after lock validation; legacy body unreachable |
|
||||
| Offline ISO build | `autoinstall/build-iso-offline.sh` | Absent; C1 readiness blocker |
|
||||
|
||||
## Candidate resolution stages
|
||||
|
||||
1. Resolve `payload-lock.candidate.json` into a reviewed `payload-lock.json` containing every object
|
||||
version, architecture, source, target, byte size, SHA-256 and license. The candidate intentionally
|
||||
fails validation while any entry is unresolved.
|
||||
The first connected resolution stage is:
|
||||
`python3 autoinstall/offline/resolve-payload-lock.py --candidate
|
||||
autoinstall/offline/payload-lock.candidate.json --source-root . --container-sources
|
||||
autoinstall/offline/container-sources.json --staging <gate2-staging> --output <candidate-lock>`.
|
||||
It creates a deterministic source archive and resolves the Linux/AMD64 OCI manifests without
|
||||
pulling image layers. Registry-declared bytes are not final measured payload bytes.
|
||||
Resolve the Ubuntu package closure from an empty package state with
|
||||
`python3 autoinstall/offline/resolve-apt-lock.py --lock <candidate-lock> --packages
|
||||
autoinstall/offline/apt-packages.txt --work <apt-staging> --output <next-candidate-lock>`.
|
||||
The result records exact `.deb` bytes but remains unresolved until repository snapshot/origin,
|
||||
license/notice, and the separate NVIDIA R570/CUDA 12.8.1 closure are approved.
|
||||
Resolve the three independent Python 3.11 wheel closures with
|
||||
`python3 autoinstall/offline/resolve-python-lock.py --lock <candidate-lock>
|
||||
--backend-requirements ansible/roles/cezen-backend/files/requirements.txt
|
||||
--platform-requirements autoinstall/offline/python-packages.txt --work <python-staging>
|
||||
--output <next-candidate-lock>`. The resolver pins its own pip version, accepts binary wheels only,
|
||||
and hashes every wheel. Index provenance, licenses, offline install/import and CUDA compatibility
|
||||
remain review gates.
|
||||
2. Run `python3 autoinstall/offline/validate-payload-lock.py
|
||||
autoinstall/offline/payload-lock.json`. No acquisition or output deletion occurs before this passes.
|
||||
3. Do not run `build-bundle.sh` expecting output. It validates the lock and then stops because
|
||||
lock-driven acquisition is not implemented. It will not fall back to the legacy mutable path.
|
||||
|
||||
## Disposable profile evidence
|
||||
|
||||
- `python-runtime-profiles.json` defines exactly-one `cpu-only` or `nvidia-cu128` selection.
|
||||
- `verify-python-profile-membership.py` emits the closed receipt consumed by the planner; structural
|
||||
test receipts require an explicit test-only switch and are rejected in production mode.
|
||||
- `prepare-python-profile-operation.py` validates membership and wheel bytes, writes the atomic journal,
|
||||
creates/verifies the checkpoint and stops at `checkpoint_ready` without mutation.
|
||||
- `install-python-profile.py` installs from a selected local wheel directory with `--no-index`, performs
|
||||
health checks, atomically activates, and supports checkpoint rollback. It is currently authorized
|
||||
only for disposable evidence paths.
|
||||
|
||||
## Mandatory stop conditions
|
||||
|
||||
Stop without assembly or mutation when any of the following is true: lock status is not `reviewed`,
|
||||
unresolved entries remain, a required object/hash/profile membership is missing, signature/trust policy
|
||||
is unverified, the journal/checkpoint is invalid, the selected hardware profile is unsupported, or the
|
||||
target is not an explicitly disposable evidence path.
|
||||
|
||||
An ISO must not be labelled offline merely because source or candidate payload bytes were embedded.
|
||||
Only a future signed, lock-driven artifact that passes the full disconnected acceptance matrix may use
|
||||
the Offline Workstation release label.
|
||||
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Evaluate a bounded, semantic, loopback-only activation health window."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import ipaddress
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
|
||||
SCHEMA = "com.cezen.nexus-one.activation-health-evidence.v1"
|
||||
MAX_RESPONSE_BYTES = 65536
|
||||
UNIT_NAME = re.compile(r"^[A-Za-z0-9_.@-]+\.service$")
|
||||
|
||||
|
||||
class HealthConfigError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def canonical(value: object) -> bytes:
|
||||
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()
|
||||
|
||||
|
||||
def validate_config(config: dict) -> list[dict]:
|
||||
if set(config) != {"schema", "components"} or config["schema"] != \
|
||||
"com.cezen.nexus-one.activation-health-profile.v1":
|
||||
raise HealthConfigError("HEALTH_PROFILE_INVALID")
|
||||
components = config["components"]
|
||||
if not isinstance(components, list) or not components:
|
||||
raise HealthConfigError("HEALTH_COMPONENTS_INVALID")
|
||||
names = set()
|
||||
result = []
|
||||
for component in components:
|
||||
if not isinstance(component, dict) or set(component) != {
|
||||
"name", "url", "required", "expected_service", "systemd_unit", "max_restarts"}:
|
||||
raise HealthConfigError("HEALTH_COMPONENT_INVALID")
|
||||
name = component["name"]
|
||||
if not isinstance(name, str) or not name or name in names:
|
||||
raise HealthConfigError("HEALTH_COMPONENT_NAME_INVALID")
|
||||
names.add(name)
|
||||
if not isinstance(component["required"], bool):
|
||||
raise HealthConfigError("HEALTH_COMPONENT_REQUIRED_INVALID")
|
||||
expected = component["expected_service"]
|
||||
if not isinstance(expected, str) or not expected:
|
||||
raise HealthConfigError("HEALTH_COMPONENT_EXPECTED_SERVICE_INVALID")
|
||||
unit = component["systemd_unit"]
|
||||
if unit is not None and (not isinstance(unit, str) or not UNIT_NAME.fullmatch(unit)):
|
||||
raise HealthConfigError("HEALTH_COMPONENT_SYSTEMD_UNIT_INVALID")
|
||||
maximum = component["max_restarts"]
|
||||
if not isinstance(maximum, int) or isinstance(maximum, bool) or maximum < 0 or maximum > 1000:
|
||||
raise HealthConfigError("HEALTH_COMPONENT_MAX_RESTARTS_INVALID")
|
||||
parsed = urllib.parse.urlparse(component["url"])
|
||||
if parsed.scheme != "http" or parsed.username or parsed.password or parsed.query \
|
||||
or parsed.fragment or not parsed.port or parsed.path == "":
|
||||
raise HealthConfigError("HEALTH_URL_INVALID")
|
||||
try:
|
||||
address = ipaddress.ip_address(parsed.hostname or "")
|
||||
except ValueError as error:
|
||||
raise HealthConfigError("HEALTH_URL_NOT_LITERAL_LOOPBACK") from error
|
||||
if not address.is_loopback:
|
||||
raise HealthConfigError("HEALTH_URL_NOT_LOOPBACK")
|
||||
result.append(dict(component))
|
||||
return result
|
||||
|
||||
|
||||
def fetch(component: dict, timeout_seconds: float) -> dict:
|
||||
request = urllib.request.Request(component["url"], headers={"Accept": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=timeout_seconds) as response:
|
||||
if response.status != 200:
|
||||
return {"ok": False, "code": f"HTTP_{response.status}"}
|
||||
content_type = response.headers.get_content_type()
|
||||
if content_type != "application/json":
|
||||
return {"ok": False, "code": "CONTENT_TYPE_INVALID"}
|
||||
payload = response.read(MAX_RESPONSE_BYTES + 1)
|
||||
if len(payload) > MAX_RESPONSE_BYTES:
|
||||
return {"ok": False, "code": "RESPONSE_TOO_LARGE"}
|
||||
except urllib.error.HTTPError as error:
|
||||
return {"ok": False, "code": f"HTTP_{error.code}"}
|
||||
except TimeoutError:
|
||||
return {"ok": False, "code": "TIMEOUT"}
|
||||
except (OSError, urllib.error.URLError):
|
||||
return {"ok": False, "code": "UNREACHABLE"}
|
||||
try:
|
||||
decoded = json.loads(payload)
|
||||
except (UnicodeDecodeError, json.JSONDecodeError):
|
||||
return {"ok": False, "code": "MALFORMED_JSON"}
|
||||
if not isinstance(decoded, dict) or set(decoded) != {"service", "status"}:
|
||||
return {"ok": False, "code": "RESPONSE_SHAPE_INVALID"}
|
||||
if decoded["service"] != component["expected_service"]:
|
||||
return {"ok": False, "code": "SERVICE_IDENTITY_MISMATCH"}
|
||||
if decoded["status"] not in {"ready", "degraded", "failed"}:
|
||||
return {"ok": False, "code": "STATUS_INVALID"}
|
||||
return {"ok": decoded["status"] == "ready", "code": decoded["status"].upper(),
|
||||
"status": decoded["status"]}
|
||||
|
||||
|
||||
def restart_state(component: dict) -> dict:
|
||||
unit = component["systemd_unit"]
|
||||
if unit is None:
|
||||
return {"ok": True, "code": "RESTART_CHECK_NOT_APPLICABLE", "restarts": 0}
|
||||
try:
|
||||
process = subprocess.run(
|
||||
["systemctl", "show", unit, "--property=NRestarts", "--property=ActiveState",
|
||||
"--property=SubState", "--no-pager"], check=True, capture_output=True, text=True,
|
||||
timeout=5,
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return {"ok": False, "code": "UNIT_STATE_UNAVAILABLE", "restarts": None}
|
||||
values = {}
|
||||
for line in process.stdout.splitlines():
|
||||
key, separator, value = line.partition("=")
|
||||
if not separator or key in values:
|
||||
return {"ok": False, "code": "UNIT_STATE_INVALID", "restarts": None}
|
||||
values[key] = value
|
||||
if set(values) != {"NRestarts", "ActiveState", "SubState"} or not values["NRestarts"].isdigit():
|
||||
return {"ok": False, "code": "UNIT_STATE_INVALID", "restarts": None}
|
||||
restarts = int(values["NRestarts"])
|
||||
if restarts > component["max_restarts"]:
|
||||
return {"ok": False, "code": "RESTART_STORM", "restarts": restarts}
|
||||
if values["ActiveState"] != "active":
|
||||
return {"ok": False, "code": "UNIT_NOT_ACTIVE", "restarts": restarts}
|
||||
return {"ok": True, "code": "RESTARTS_OK", "restarts": restarts}
|
||||
|
||||
|
||||
def atomic_evidence(path: Path, evidence: dict) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
|
||||
try:
|
||||
os.fchmod(fd, 0o600)
|
||||
with os.fdopen(fd, "wb") as stream:
|
||||
stream.write(canonical(evidence))
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
os.replace(temporary, path)
|
||||
directory_fd = os.open(path.parent, os.O_RDONLY)
|
||||
try:
|
||||
os.fsync(directory_fd)
|
||||
finally:
|
||||
os.close(directory_fd)
|
||||
finally:
|
||||
if os.path.exists(temporary):
|
||||
os.unlink(temporary)
|
||||
|
||||
|
||||
def evaluate(config: dict, operation_id: str, samples: int, interval_seconds: float,
|
||||
timeout_seconds: float, window_timeout_seconds: float, allow_degraded: bool,
|
||||
fetcher: Callable = fetch, restart_checker: Callable = restart_state,
|
||||
sleeper: Callable = time.sleep, clock: Callable = time.monotonic) -> dict:
|
||||
components = validate_config(config)
|
||||
if not operation_id or any(character not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_." for character in operation_id):
|
||||
raise HealthConfigError("OPERATION_ID_INVALID")
|
||||
if samples < 1 or samples > 100 or interval_seconds < 0 or timeout_seconds <= 0 \
|
||||
or window_timeout_seconds <= 0 or timeout_seconds > window_timeout_seconds:
|
||||
raise HealthConfigError("HEALTH_WINDOW_BOUNDS_INVALID")
|
||||
records = []
|
||||
outcome = "ready"
|
||||
deadline = clock() + window_timeout_seconds
|
||||
for sample in range(1, samples + 1):
|
||||
for component in components:
|
||||
remaining = deadline - clock()
|
||||
if remaining <= 0:
|
||||
result = {"ok": False, "code": "WINDOW_TIMEOUT"}
|
||||
restart = {"code": "NOT_CHECKED", "restarts": None}
|
||||
else:
|
||||
restart = restart_checker(component)
|
||||
if restart.get("ok") is not True:
|
||||
result = restart
|
||||
else:
|
||||
remaining = deadline - clock()
|
||||
result = {"ok": False, "code": "WINDOW_TIMEOUT"} if remaining <= 0 else \
|
||||
fetcher(component, min(timeout_seconds, remaining))
|
||||
if clock() > deadline:
|
||||
result = {"ok": False, "code": "WINDOW_TIMEOUT"}
|
||||
status = result.get("status")
|
||||
accepted_degraded = status == "degraded" and not component["required"] and allow_degraded
|
||||
accepted = result.get("ok") is True or accepted_degraded
|
||||
if accepted_degraded:
|
||||
outcome = "degraded"
|
||||
records.append({"sample": sample, "component": component["name"],
|
||||
"required": component["required"], "accepted": accepted,
|
||||
"code": result.get("code", "PROBE_RESULT_INVALID"),
|
||||
"restart_code": restart.get("code"),
|
||||
"restart_count": restart.get("restarts")})
|
||||
if not accepted:
|
||||
outcome = "rejected"
|
||||
return {"schema": SCHEMA, "operation_id": operation_id,
|
||||
"profile_sha256": hashlib.sha256(canonical(config)).hexdigest(),
|
||||
"samples_required": samples, "samples_completed": sample,
|
||||
"allow_degraded": allow_degraded, "outcome": outcome, "checks": records}
|
||||
if sample != samples:
|
||||
if clock() + interval_seconds > deadline:
|
||||
records.append({"sample": sample, "component": "window", "required": True,
|
||||
"accepted": False, "code": "WINDOW_TIMEOUT",
|
||||
"restart_code": "NOT_APPLICABLE", "restart_count": None})
|
||||
return {"schema": SCHEMA, "operation_id": operation_id,
|
||||
"profile_sha256": hashlib.sha256(canonical(config)).hexdigest(),
|
||||
"samples_required": samples, "samples_completed": sample,
|
||||
"allow_degraded": allow_degraded, "outcome": "rejected",
|
||||
"checks": records}
|
||||
sleeper(interval_seconds)
|
||||
return {"schema": SCHEMA, "operation_id": operation_id,
|
||||
"profile_sha256": hashlib.sha256(canonical(config)).hexdigest(),
|
||||
"samples_required": samples, "samples_completed": samples,
|
||||
"allow_degraded": allow_degraded, "outcome": outcome, "checks": records}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None, fetcher: Callable = fetch,
|
||||
restart_checker: Callable = restart_state, sleeper: Callable = time.sleep,
|
||||
clock: Callable = time.monotonic) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--operation-id", required=True)
|
||||
parser.add_argument("--profile-config", type=Path, required=True)
|
||||
parser.add_argument("--samples", type=int, required=True)
|
||||
parser.add_argument("--interval-seconds", type=float, required=True)
|
||||
parser.add_argument("--timeout-seconds", type=float, required=True)
|
||||
parser.add_argument("--window-timeout-seconds", type=float, required=True)
|
||||
parser.add_argument("--evidence", type=Path, required=True)
|
||||
parser.add_argument("--allow-degraded", action="store_true")
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
config = json.loads(args.profile_config.read_text(encoding="utf-8"))
|
||||
evidence = evaluate(config, args.operation_id, args.samples, args.interval_seconds,
|
||||
args.timeout_seconds, args.window_timeout_seconds, args.allow_degraded,
|
||||
fetcher, restart_checker, sleeper, clock)
|
||||
atomic_evidence(args.evidence, evidence)
|
||||
except (HealthConfigError, OSError, json.JSONDecodeError) as error:
|
||||
print(json.dumps({"accepted": False, "code": str(error)}, sort_keys=True))
|
||||
return 2
|
||||
print(json.dumps({"accepted": evidence["outcome"] in {"ready", "degraded"},
|
||||
"outcome": evidence["outcome"]}, sort_keys=True))
|
||||
return 0 if evidence["outcome"] in {"ready", "degraded"} else 23
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Atomically persist the Python profile operation journal."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import fcntl
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class JournalError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
TRANSITIONS = {
|
||||
None: {"profile_selected"},
|
||||
"profile_selected": {"checkpoint_ready", "rolled_back"},
|
||||
"checkpoint_ready": {"installing", "rolled_back"},
|
||||
"installing": {"installed", "rollback_required"},
|
||||
"rollback_required": {"rolled_back"},
|
||||
"installed": set(),
|
||||
"rolled_back": set(),
|
||||
}
|
||||
|
||||
|
||||
def canonical(record: dict) -> bytes:
|
||||
return (json.dumps(record, sort_keys=True, separators=(",", ":")) + "\n").encode()
|
||||
|
||||
|
||||
def digest_record(record: dict) -> str:
|
||||
unsigned = {key: value for key, value in record.items() if key != "record_sha256"}
|
||||
return hashlib.sha256(canonical(unsigned)).hexdigest()
|
||||
|
||||
|
||||
def read_verified(path: Path) -> dict | None:
|
||||
if not path.exists():
|
||||
return None
|
||||
record = json.loads(path.read_text(encoding="utf-8"))
|
||||
if record.get("record_sha256") != digest_record(record):
|
||||
raise JournalError("JOURNAL_DIGEST_INVALID")
|
||||
return record
|
||||
|
||||
|
||||
def next_record(current: dict | None, expected_generation: int, operation_id: str,
|
||||
profile: str, manifest_sha256: str, state: str,
|
||||
checkpoint_id: str | None) -> dict:
|
||||
generation = current.get("generation", 0) if current else 0
|
||||
if generation != expected_generation:
|
||||
raise JournalError("JOURNAL_GENERATION_STALE")
|
||||
prior_state = current.get("state") if current else None
|
||||
if state not in TRANSITIONS.get(prior_state, set()):
|
||||
raise JournalError("JOURNAL_TRANSITION_INVALID")
|
||||
if current and (current["operation_id"] != operation_id or current["selected_profile"] != profile
|
||||
or current["manifest_sha256"] != manifest_sha256):
|
||||
raise JournalError("JOURNAL_IDENTITY_CHANGED")
|
||||
inherited_checkpoint = current.get("checkpoint_id") if current else None
|
||||
checkpoint = checkpoint_id or inherited_checkpoint
|
||||
if state in {"checkpoint_ready", "installing", "installed", "rollback_required", "rolled_back"} \
|
||||
and prior_state != "profile_selected" and not checkpoint:
|
||||
raise JournalError("CHECKPOINT_ID_REQUIRED")
|
||||
if state in {"checkpoint_ready", "installing", "installed", "rollback_required"} and not checkpoint:
|
||||
raise JournalError("CHECKPOINT_ID_REQUIRED")
|
||||
record = {
|
||||
"schema": "com.cezen.nexus-one.python-profile-journal.v1",
|
||||
"generation": generation + 1,
|
||||
"operation_id": operation_id,
|
||||
"selected_profile": profile,
|
||||
"manifest_sha256": manifest_sha256,
|
||||
"state": state,
|
||||
"checkpoint_id": checkpoint,
|
||||
"previous_record_sha256": current.get("record_sha256") if current else None,
|
||||
}
|
||||
record["record_sha256"] = digest_record(record)
|
||||
return record
|
||||
|
||||
|
||||
def atomic_write(path: Path, record: dict) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
|
||||
try:
|
||||
os.fchmod(fd, 0o600)
|
||||
with os.fdopen(fd, "wb", closefd=True) as stream:
|
||||
stream.write(canonical(record))
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
os.replace(temporary, path)
|
||||
directory_fd = os.open(path.parent, os.O_RDONLY)
|
||||
try:
|
||||
os.fsync(directory_fd)
|
||||
finally:
|
||||
os.close(directory_fd)
|
||||
finally:
|
||||
if os.path.exists(temporary):
|
||||
os.unlink(temporary)
|
||||
|
||||
|
||||
def update(path: Path, **values) -> dict:
|
||||
lock_path = path.with_suffix(path.suffix + ".lock")
|
||||
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with lock_path.open("a+b") as lock:
|
||||
os.chmod(lock_path, 0o600)
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
current = read_verified(path)
|
||||
record = next_record(current, **values)
|
||||
atomic_write(path, record)
|
||||
return record
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("path", type=Path)
|
||||
parser.add_argument("--expected-generation", type=int, required=True)
|
||||
parser.add_argument("--operation-id", required=True)
|
||||
parser.add_argument("--profile", choices=("cpu-only", "nvidia-cu128"), required=True)
|
||||
parser.add_argument("--manifest-sha256", required=True)
|
||||
parser.add_argument("--state", choices=tuple(state for state in TRANSITIONS if state), required=True)
|
||||
parser.add_argument("--checkpoint-id")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
record = update(
|
||||
args.path, expected_generation=args.expected_generation,
|
||||
operation_id=args.operation_id, profile=args.profile,
|
||||
manifest_sha256=args.manifest_sha256, state=args.state,
|
||||
checkpoint_id=args.checkpoint_id,
|
||||
)
|
||||
except (JournalError, json.JSONDecodeError) as error:
|
||||
print(json.dumps({"written": False, "code": str(error)}, sort_keys=True))
|
||||
return 1
|
||||
print(json.dumps({"written": True, **record}, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stage all network-fetched Server Starter inputs for a disconnected install.
|
||||
# Stage reviewed, immutable Offline Workstation inputs for a disconnected install.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -7,10 +7,20 @@ PACKAGE_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
OUTPUT_DIR="${OFFLINE_OUTPUT_DIR:-$PACKAGE_DIR/autoinstall/offline-output}"
|
||||
PAYLOAD_DIR="$OUTPUT_DIR/payload"
|
||||
MANIFEST_DIR="$OUTPUT_DIR/manifest"
|
||||
PAYLOAD_LOCK="${OFFLINE_PAYLOAD_LOCK:-$SCRIPT_DIR/payload-lock.json}"
|
||||
|
||||
# shellcheck source=payload.env
|
||||
source "$SCRIPT_DIR/payload.env"
|
||||
|
||||
# Gate 2 preflight MUST run before privilege checks, network access, output deletion, or acquisition.
|
||||
python3 "$SCRIPT_DIR/validate-payload-lock.py" "$PAYLOAD_LOCK"
|
||||
|
||||
# The legacy acquisition path below does not yet consume the reviewed lock. Keep it unreachable until
|
||||
# each acquisition step verifies bytes against the lock before placing them in trusted staging.
|
||||
echo "ERROR: immutable lock preflight passed, but lock-driven acquisition is not implemented yet." >&2
|
||||
echo "ERROR: refusing to execute the legacy mutable bundle builder." >&2
|
||||
exit 1
|
||||
|
||||
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
|
||||
echo "ERROR: run as root on a connected Ubuntu 22.04 staging host." >&2
|
||||
exit 1
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{"id":"open-webui","version":"resolve-for-gate2","source":"ghcr.io/open-webui/open-webui:main","license":"REVIEW-REQUIRED"},
|
||||
{"id":"vllm","version":"0.10.2","source":"vllm/vllm-openai:v0.10.2","license":"Apache-2.0"},
|
||||
{"id":"dcgm-exporter","version":"3.3.0-3.2.0-ubuntu22.04","source":"nvcr.io/nvidia/k8s/dcgm-exporter:3.3.0-3.2.0-ubuntu22.04","license":"REVIEW-REQUIRED"},
|
||||
{"id":"prometheus","version":"resolve-for-gate2","source":"prom/prometheus:latest","license":"Apache-2.0"},
|
||||
{"id":"node-exporter","version":"resolve-for-gate2","source":"prom/node-exporter:latest","license":"Apache-2.0"},
|
||||
{"id":"grafana","version":"resolve-for-gate2","source":"grafana/grafana:latest","license":"AGPL-3.0-only"}
|
||||
]
|
||||
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install a prepared Python profile offline into an isolated target with rollback."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def load_module(name: str, filename: str):
|
||||
spec = importlib.util.spec_from_file_location(name, HERE / filename)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec.loader
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
journal_api = load_module("install_profile_journal", "atomic-profile-journal.py")
|
||||
checkpoint_api = load_module("install_profile_checkpoint", "python-environment-checkpoint.py")
|
||||
|
||||
|
||||
def fault(point: str) -> None:
|
||||
if os.environ.get("NEXUS_ALLOW_TEST_FAULTS") == "1" \
|
||||
and os.environ.get("NEXUS_TEST_CRASH_AT") == point:
|
||||
os._exit(97)
|
||||
|
||||
|
||||
def filtered_requirements(source: Path, profile: str, destination: Path) -> None:
|
||||
excluded = {"chromadb"}
|
||||
if profile == "cpu-only":
|
||||
excluded.add("bitsandbytes")
|
||||
lines = []
|
||||
for raw in source.read_text().splitlines():
|
||||
stripped = raw.split("#", 1)[0].strip()
|
||||
name = stripped.split("=", 1)[0].strip().lower().replace("_", "-")
|
||||
if stripped and name not in excluded:
|
||||
lines.append(stripped)
|
||||
destination.write_text("\n".join(lines) + "\n")
|
||||
|
||||
|
||||
def health(python: Path, profile: str) -> None:
|
||||
base = "import torch,torchaudio,torchvision,transformers,peft,accelerate,sentence_transformers;"
|
||||
if profile == "cpu-only":
|
||||
check = (base + "assert torch.__version__=='2.8.0+cpu';assert torch.version.cuda is None;"
|
||||
"assert not torch.cuda.is_available();assert (torch.tensor([2.0])*2).item()==4.0")
|
||||
else:
|
||||
check = (base + "import bitsandbytes;assert torch.__version__.startswith('2.8.0');"
|
||||
"assert torch.version.cuda=='12.8';assert torch.cuda.is_available();"
|
||||
"x=torch.tensor([2.0],device='cuda');assert (x*2).item()==4.0;torch.cuda.synchronize()")
|
||||
subprocess.run([str(python), "-c", check], check=True)
|
||||
|
||||
|
||||
def validate_service_probe(probe: Path | None) -> None:
|
||||
if probe is None:
|
||||
return
|
||||
if not probe.is_absolute():
|
||||
raise ValueError("SERVICE_HEALTH_PROBE_NOT_ABSOLUTE")
|
||||
if probe.is_symlink() or not probe.is_file():
|
||||
raise ValueError("SERVICE_HEALTH_PROBE_NOT_REGULAR")
|
||||
if not stat.S_IMODE(probe.stat().st_mode) & 0o111:
|
||||
raise ValueError("SERVICE_HEALTH_PROBE_NOT_EXECUTABLE")
|
||||
|
||||
|
||||
def service_health(probe: Path | None, target: Path, profile: str, timeout_seconds: int) -> None:
|
||||
validate_service_probe(probe)
|
||||
if probe is not None:
|
||||
subprocess.run([str(probe), str(target), profile], check=True,
|
||||
timeout=timeout_seconds)
|
||||
|
||||
|
||||
def install(journal_path: Path, checkpoint: Path, target: Path, wheel_dir: Path,
|
||||
requirements: Path, python_executable: Path, service_health_probe: Path | None = None,
|
||||
service_health_timeout_seconds: int = 300) -> dict:
|
||||
if service_health_timeout_seconds < 1 or service_health_timeout_seconds > 3600:
|
||||
raise ValueError("SERVICE_HEALTH_TIMEOUT_INVALID")
|
||||
validate_service_probe(service_health_probe)
|
||||
current = journal_api.read_verified(journal_path)
|
||||
if not current or current["state"] != "checkpoint_ready":
|
||||
raise ValueError("JOURNAL_NOT_CHECKPOINT_READY")
|
||||
checkpoint_api.verify(checkpoint, target)
|
||||
identity = dict(operation_id=current["operation_id"], profile=current["selected_profile"],
|
||||
manifest_sha256=current["manifest_sha256"])
|
||||
current = journal_api.update(journal_path, expected_generation=current["generation"],
|
||||
state="installing", checkpoint_id=current["checkpoint_id"], **identity)
|
||||
temporary = Path(tempfile.mkdtemp(prefix=f".{target.name}.install.", dir=target.parent))
|
||||
shutil.rmtree(temporary)
|
||||
activated = False
|
||||
req: Path | None = None
|
||||
try:
|
||||
subprocess.run([str(python_executable), "-m", "venv", str(temporary)], check=True)
|
||||
req = temporary.parent / f".{temporary.name}.requirements.txt"
|
||||
filtered_requirements(requirements, identity["profile"], req)
|
||||
subprocess.run([str(temporary / "bin/pip"), "install", "--no-index",
|
||||
f"--find-links={wheel_dir}", "-r", str(req)], check=True)
|
||||
req.unlink()
|
||||
health(temporary / "bin/python", identity["profile"])
|
||||
checkpoint_api.fsync_tree(temporary)
|
||||
previous = target.with_name(f".{target.name}.install-previous")
|
||||
if previous.exists():
|
||||
raise ValueError("INSTALL_PREVIOUS_EXISTS")
|
||||
if target.exists():
|
||||
os.replace(target, previous)
|
||||
os.replace(temporary, target)
|
||||
activated = True
|
||||
directory_fd = os.open(target.parent, os.O_RDONLY)
|
||||
try:
|
||||
os.fsync(directory_fd)
|
||||
finally:
|
||||
os.close(directory_fd)
|
||||
fault("after_activation")
|
||||
health(target / "bin/python", identity["profile"])
|
||||
service_health(service_health_probe, target, identity["profile"],
|
||||
service_health_timeout_seconds)
|
||||
current = journal_api.update(journal_path, expected_generation=current["generation"],
|
||||
state="installed", checkpoint_id=current["checkpoint_id"], **identity)
|
||||
return {"installed": True, "generation": current["generation"],
|
||||
"selected_profile": identity["profile"]}
|
||||
except Exception:
|
||||
current = journal_api.update(journal_path, expected_generation=current["generation"],
|
||||
state="rollback_required", checkpoint_id=current["checkpoint_id"],
|
||||
**identity)
|
||||
if activated:
|
||||
checkpoint_api.restore(checkpoint, target)
|
||||
else:
|
||||
checkpoint_api.verify(checkpoint, target)
|
||||
journal_api.update(journal_path, expected_generation=current["generation"], state="rolled_back",
|
||||
checkpoint_id=current["checkpoint_id"], **identity)
|
||||
raise
|
||||
finally:
|
||||
if temporary.exists():
|
||||
shutil.rmtree(temporary)
|
||||
if req is not None and req.exists():
|
||||
req.unlink()
|
||||
|
||||
|
||||
def recover(journal_path: Path, checkpoint: Path, target: Path) -> dict:
|
||||
current = journal_api.read_verified(journal_path)
|
||||
if not current or current["state"] not in {"installing", "rollback_required"}:
|
||||
raise ValueError("JOURNAL_NOT_RECOVERABLE")
|
||||
identity = dict(operation_id=current["operation_id"], profile=current["selected_profile"],
|
||||
manifest_sha256=current["manifest_sha256"])
|
||||
if current["state"] == "installing":
|
||||
current = journal_api.update(journal_path, expected_generation=current["generation"],
|
||||
state="rollback_required", checkpoint_id=current["checkpoint_id"],
|
||||
**identity)
|
||||
manifest = checkpoint_api.verify(checkpoint, target)
|
||||
if target.exists() and checkpoint_api.inventory(target) != manifest["entries"]:
|
||||
checkpoint_api.restore(checkpoint, target)
|
||||
else:
|
||||
checkpoint_api.recover_restore(checkpoint, target)
|
||||
current = journal_api.update(journal_path, expected_generation=current["generation"],
|
||||
state="rolled_back", checkpoint_id=current["checkpoint_id"], **identity)
|
||||
return {"recovered": True, "generation": current["generation"], "state": current["state"]}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--journal", type=Path, required=True)
|
||||
parser.add_argument("--checkpoint", type=Path, required=True)
|
||||
parser.add_argument("--target", type=Path, required=True)
|
||||
parser.add_argument("--wheel-dir", type=Path, required=True)
|
||||
parser.add_argument("--requirements", type=Path, required=True)
|
||||
parser.add_argument("--python", type=Path, required=True)
|
||||
parser.add_argument("--service-health-probe", type=Path)
|
||||
parser.add_argument("--service-health-timeout-seconds", type=int, default=300)
|
||||
parser.add_argument("--recover-only", action="store_true")
|
||||
args = parser.parse_args()
|
||||
if args.recover_only:
|
||||
result = recover(args.journal, args.checkpoint, args.target)
|
||||
else:
|
||||
result = install(args.journal, args.checkpoint, args.target, args.wheel_dir,
|
||||
args.requirements, args.python, args.service_health_probe,
|
||||
args.service_health_timeout_seconds)
|
||||
print(result)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"schema": "nexus-one-ai.offline-workstation-payload-lock.v1",
|
||||
"product": "offline-workstation",
|
||||
"architecture": "amd64",
|
||||
"ubuntu_release": "22.04.5",
|
||||
"approval_record": "CT-G1-2026-009",
|
||||
"status": "candidate",
|
||||
"unresolved": [
|
||||
"ubuntu ISO bytes and digest",
|
||||
"exact APT dependency closure",
|
||||
"hash-locked Python dependency closure",
|
||||
"Miniconda or replacement runtime archive",
|
||||
"Ollama runtime archive",
|
||||
"OCI image manifests and layers by digest",
|
||||
"approved model files, notices, bytes and digests",
|
||||
"source snapshot archive bytes and digest"
|
||||
],
|
||||
"objects": {
|
||||
"apt": [],
|
||||
"python": [],
|
||||
"runtime": [],
|
||||
"containers": [],
|
||||
"models": [],
|
||||
"source": []
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
# Offline Server Starter release inputs. Mutable tags are resolved to immutable
|
||||
# image digests in the generated provenance file at bundle-build time.
|
||||
# Legacy Offline Workstation inputs retained only for migration into payload-lock.json.
|
||||
# build-bundle.sh refuses to acquire from these mutable values.
|
||||
OFFLINE_SCHEMA="cezen.offline_payload.v1"
|
||||
OFFLINE_TIER="workstation"
|
||||
UBUNTU_RELEASE="22.04.5"
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Plan fail-closed Python runtime profile selection without mutating the host."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class PlanError(ValueError):
|
||||
def __init__(self, code: str, message: str):
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
|
||||
|
||||
def choose_profile(probe: dict, requested: str) -> str:
|
||||
if probe.get("architecture") != "amd64":
|
||||
raise PlanError("ARCHITECTURE_UNSUPPORTED", "Offline Workstation profile requires AMD64")
|
||||
nvidia = probe.get("nvidia", {})
|
||||
ready = all(nvidia.get(key) is True for key in ("present", "supported", "driver_ready"))
|
||||
if requested == "cpu-only":
|
||||
return "cpu-only"
|
||||
if requested == "nvidia-cu128":
|
||||
if not ready:
|
||||
raise PlanError("NVIDIA_PROFILE_NOT_READY", "requested NVIDIA profile is not ready")
|
||||
return "nvidia-cu128"
|
||||
if requested != "auto":
|
||||
raise PlanError("PROFILE_UNKNOWN", f"unknown requested profile: {requested}")
|
||||
return "nvidia-cu128" if ready else "cpu-only"
|
||||
|
||||
|
||||
def plan(probe: dict, receipt: dict, requested: str, operation_id: str, journal: dict | None,
|
||||
allow_test_receipt: bool = False) -> dict:
|
||||
if receipt.get("schema") != "com.cezen.nexus-one.profile-membership-verification-receipt.v1":
|
||||
raise PlanError("MEMBERSHIP_RECEIPT_INVALID", "membership receipt schema is invalid")
|
||||
if receipt.get("result") != "verified":
|
||||
raise PlanError("MEMBERSHIP_UNVERIFIED", "signed profile membership is not verified")
|
||||
if receipt.get("environment") != "production" and not allow_test_receipt:
|
||||
raise PlanError("TEST_RECEIPT_FORBIDDEN", "test receipt is forbidden for production planning")
|
||||
if receipt.get("namespace") != "offline-workstation/python-runtime-profiles":
|
||||
raise PlanError("MEMBERSHIP_RECEIPT_INVALID", "membership namespace is invalid")
|
||||
if not receipt.get("manifest_sha256"):
|
||||
raise PlanError("MEMBERSHIP_UNVERIFIED", "verified membership lacks manifest identity")
|
||||
selected = choose_profile(probe, requested)
|
||||
if selected not in receipt.get("profiles", []):
|
||||
raise PlanError("PROFILE_NOT_IN_SIGNED_MEMBERSHIP", f"{selected} is not signed membership")
|
||||
|
||||
if journal:
|
||||
state = journal.get("state")
|
||||
previous = journal.get("selected_profile")
|
||||
if state != "rolled_back":
|
||||
if journal.get("operation_id") != operation_id:
|
||||
raise PlanError("ACTIVE_OPERATION_EXISTS", "another profile operation is active")
|
||||
if previous != selected:
|
||||
raise PlanError(
|
||||
"PROFILE_CHANGE_REQUIRES_ROLLBACK",
|
||||
f"journal selected {previous}; requested selection is {selected}",
|
||||
)
|
||||
return {
|
||||
"action": "resume",
|
||||
"operation_id": operation_id,
|
||||
"selected_profile": selected,
|
||||
"resume_from": state,
|
||||
"manifest_sha256": receipt["manifest_sha256"],
|
||||
"mutation_allowed_after_journal_write": True,
|
||||
}
|
||||
if journal.get("operation_id") == operation_id:
|
||||
raise PlanError("NEW_OPERATION_REQUIRED", "rolled-back selection requires a new operation id")
|
||||
|
||||
return {
|
||||
"action": "start",
|
||||
"operation_id": operation_id,
|
||||
"selected_profile": selected,
|
||||
"journal_state": "profile_selected",
|
||||
"checkpoint_required": True,
|
||||
"manifest_sha256": receipt["manifest_sha256"],
|
||||
"mutation_allowed_after_journal_write": True,
|
||||
}
|
||||
|
||||
|
||||
def load(path: Path | None) -> dict | None:
|
||||
return json.loads(path.read_text(encoding="utf-8")) if path else None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--probe", type=Path, required=True)
|
||||
parser.add_argument("--membership-receipt", type=Path, required=True)
|
||||
parser.add_argument("--requested", choices=("auto", "cpu-only", "nvidia-cu128"), default="auto")
|
||||
parser.add_argument("--operation-id", required=True)
|
||||
parser.add_argument("--journal", type=Path)
|
||||
parser.add_argument("--allow-test-receipt", action="store_true")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
result = plan(load(args.probe), load(args.membership_receipt), args.requested,
|
||||
args.operation_id, load(args.journal), args.allow_test_receipt)
|
||||
except PlanError as error:
|
||||
print(json.dumps({"allowed": False, "code": error.code, "message": str(error)}, sort_keys=True))
|
||||
return 1
|
||||
print(json.dumps({"allowed": True, **result}, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Orchestrate verified profile selection through durable checkpoint readiness only."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def load_module(name: str, filename: str):
|
||||
spec = importlib.util.spec_from_file_location(name, HERE / filename)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec.loader
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
planner = load_module("profile_planner", "plan-python-runtime-profile.py")
|
||||
journal_api = load_module("profile_journal", "atomic-profile-journal.py")
|
||||
checkpoint_api = load_module("profile_checkpoint", "python-environment-checkpoint.py")
|
||||
profile_api = load_module("profile_validator", "validate-python-runtime-profile.py")
|
||||
|
||||
|
||||
def prepare(probe: dict, receipt: dict, requested: str, operation_id: str, journal_path: Path,
|
||||
target: Path, checkpoint: Path, contract: dict, wheel_dir: Path,
|
||||
allow_test_receipt: bool = False) -> dict:
|
||||
current = journal_api.read_verified(journal_path)
|
||||
planned = planner.plan(probe, receipt, requested, operation_id, current, allow_test_receipt)
|
||||
selected = planned["selected_profile"]
|
||||
errors = profile_api.validate(contract, selected, wheel_dir)
|
||||
if errors:
|
||||
raise ValueError("PROFILE_PAYLOAD_INVALID:" + "|".join(errors))
|
||||
|
||||
if current is None:
|
||||
current = journal_api.update(
|
||||
journal_path, expected_generation=0, operation_id=operation_id, profile=selected,
|
||||
manifest_sha256=receipt["manifest_sha256"], state="profile_selected", checkpoint_id=None,
|
||||
)
|
||||
if current["state"] == "profile_selected":
|
||||
if checkpoint.exists():
|
||||
checkpoint_manifest = checkpoint_api.verify(checkpoint, target)
|
||||
else:
|
||||
checkpoint_manifest = checkpoint_api.create(target, checkpoint, operation_id)
|
||||
current = journal_api.update(
|
||||
journal_path, expected_generation=current["generation"], operation_id=operation_id,
|
||||
profile=selected, manifest_sha256=receipt["manifest_sha256"], state="checkpoint_ready",
|
||||
checkpoint_id=checkpoint_manifest["inventory_sha256"],
|
||||
)
|
||||
if current["state"] != "checkpoint_ready":
|
||||
raise ValueError(f"OPERATION_NOT_PREPARABLE:{current['state']}")
|
||||
return {"ready": True, "selected_profile": selected, "generation": current["generation"],
|
||||
"checkpoint_id": current["checkpoint_id"], "mutation_performed": False}
|
||||
|
||||
|
||||
def read(path: Path) -> dict:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--probe", type=Path, required=True)
|
||||
parser.add_argument("--receipt", type=Path, required=True)
|
||||
parser.add_argument("--requested", choices=("auto", "cpu-only", "nvidia-cu128"), default="auto")
|
||||
parser.add_argument("--operation-id", required=True)
|
||||
parser.add_argument("--journal", type=Path, required=True)
|
||||
parser.add_argument("--target", type=Path, required=True)
|
||||
parser.add_argument("--checkpoint", type=Path, required=True)
|
||||
parser.add_argument("--profile-contract", type=Path, required=True)
|
||||
parser.add_argument("--wheel-dir", type=Path, required=True)
|
||||
parser.add_argument("--allow-test-receipt", action="store_true")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
result = prepare(
|
||||
read(args.probe), read(args.receipt), args.requested, args.operation_id, args.journal,
|
||||
args.target, args.checkpoint, read(args.profile_contract), args.wheel_dir,
|
||||
args.allow_test_receipt,
|
||||
)
|
||||
except Exception as error:
|
||||
print(json.dumps({"ready": False, "code": str(error)}, sort_keys=True))
|
||||
return 1
|
||||
print(json.dumps(result, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create, verify and restore a hashed Python-environment directory checkpoint."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class CheckpointError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def fault(point: str) -> None:
|
||||
if os.environ.get("NEXUS_ALLOW_TEST_FAULTS") == "1" \
|
||||
and os.environ.get("NEXUS_TEST_CRASH_AT") == point:
|
||||
os._exit(97)
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def inventory(root: Path) -> list[dict]:
|
||||
result = []
|
||||
if not root.exists():
|
||||
return result
|
||||
for path in sorted(root.rglob("*")):
|
||||
relative = path.relative_to(root).as_posix()
|
||||
mode = path.lstat().st_mode & 0o7777
|
||||
if path.is_symlink():
|
||||
result.append({"path": relative, "type": "symlink", "mode": mode,
|
||||
"target": os.readlink(path)})
|
||||
elif path.is_file():
|
||||
result.append({"path": relative, "type": "file", "mode": mode,
|
||||
"bytes": path.stat().st_size, "sha256": sha256(path)})
|
||||
elif path.is_dir():
|
||||
result.append({"path": relative, "type": "directory", "mode": mode})
|
||||
else:
|
||||
raise CheckpointError(f"UNSUPPORTED_ENTRY:{relative}")
|
||||
return result
|
||||
|
||||
|
||||
def canonical(value: dict) -> bytes:
|
||||
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()
|
||||
|
||||
|
||||
def fsync_tree(root: Path) -> None:
|
||||
for path in root.rglob("*"):
|
||||
if path.is_file() and not path.is_symlink():
|
||||
fd = os.open(path, os.O_RDONLY)
|
||||
try:
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
for path in sorted((p for p in root.rglob("*") if p.is_dir()), reverse=True):
|
||||
fd = os.open(path, os.O_RDONLY)
|
||||
try:
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
fd = os.open(root, os.O_RDONLY)
|
||||
try:
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def create(target: Path, checkpoint: Path, operation_id: str) -> dict:
|
||||
if checkpoint.exists():
|
||||
raise CheckpointError("CHECKPOINT_ALREADY_EXISTS")
|
||||
checkpoint.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = Path(tempfile.mkdtemp(prefix=f".{checkpoint.name}.", dir=checkpoint.parent))
|
||||
try:
|
||||
payload = temporary / "payload"
|
||||
existed = target.exists()
|
||||
if existed:
|
||||
if not target.is_dir() or target.is_symlink():
|
||||
raise CheckpointError("TARGET_NOT_DIRECTORY")
|
||||
shutil.copytree(target, payload, symlinks=True)
|
||||
manifest = {
|
||||
"schema": "com.cezen.nexus-one.python-environment-checkpoint.v1",
|
||||
"operation_id": operation_id,
|
||||
"target": str(target),
|
||||
"target_existed": existed,
|
||||
"entries": inventory(payload),
|
||||
}
|
||||
manifest["inventory_sha256"] = hashlib.sha256(canonical(manifest)).hexdigest()
|
||||
(temporary / "manifest.json").write_bytes(canonical(manifest))
|
||||
fsync_tree(temporary)
|
||||
fault("create_before_replace")
|
||||
os.replace(temporary, checkpoint)
|
||||
fault("create_after_replace")
|
||||
directory_fd = os.open(checkpoint.parent, os.O_RDONLY)
|
||||
try:
|
||||
os.fsync(directory_fd)
|
||||
finally:
|
||||
os.close(directory_fd)
|
||||
return manifest
|
||||
finally:
|
||||
if temporary.exists():
|
||||
shutil.rmtree(temporary)
|
||||
|
||||
|
||||
def verify(checkpoint: Path, expected_target: Path | None = None) -> dict:
|
||||
manifest = json.loads((checkpoint / "manifest.json").read_text())
|
||||
recorded = manifest.pop("inventory_sha256", None)
|
||||
if recorded != hashlib.sha256(canonical(manifest)).hexdigest():
|
||||
raise CheckpointError("CHECKPOINT_MANIFEST_INVALID")
|
||||
manifest["inventory_sha256"] = recorded
|
||||
if expected_target is not None and manifest["target"] != str(expected_target):
|
||||
raise CheckpointError("CHECKPOINT_TARGET_MISMATCH")
|
||||
actual = inventory(checkpoint / "payload")
|
||||
if actual != manifest["entries"]:
|
||||
raise CheckpointError("CHECKPOINT_PAYLOAD_INVALID")
|
||||
return manifest
|
||||
|
||||
|
||||
def restore(checkpoint: Path, target: Path) -> dict:
|
||||
manifest = verify(checkpoint, target)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not manifest["target_existed"]:
|
||||
if target.exists():
|
||||
quarantine = target.with_name(f".{target.name}.rollback-discard")
|
||||
if quarantine.exists():
|
||||
raise CheckpointError("ROLLBACK_QUARANTINE_EXISTS")
|
||||
os.replace(target, quarantine)
|
||||
fault("restore_after_quarantine")
|
||||
directory_fd = os.open(target.parent, os.O_RDONLY)
|
||||
try:
|
||||
os.fsync(directory_fd)
|
||||
finally:
|
||||
os.close(directory_fd)
|
||||
return manifest
|
||||
temporary = Path(tempfile.mkdtemp(prefix=f".{target.name}.restore.", dir=target.parent))
|
||||
shutil.rmtree(temporary)
|
||||
shutil.copytree(checkpoint / "payload", temporary, symlinks=True)
|
||||
fsync_tree(temporary)
|
||||
if target.exists():
|
||||
quarantine = target.with_name(f".{target.name}.rollback-discard")
|
||||
if quarantine.exists():
|
||||
raise CheckpointError("ROLLBACK_QUARANTINE_EXISTS")
|
||||
os.replace(target, quarantine)
|
||||
fault("restore_after_quarantine")
|
||||
os.replace(temporary, target)
|
||||
fault("restore_after_replace")
|
||||
directory_fd = os.open(target.parent, os.O_RDONLY)
|
||||
try:
|
||||
os.fsync(directory_fd)
|
||||
finally:
|
||||
os.close(directory_fd)
|
||||
return manifest
|
||||
|
||||
|
||||
def recover_restore(checkpoint: Path, target: Path) -> dict:
|
||||
manifest = verify(checkpoint, target)
|
||||
quarantine = target.with_name(f".{target.name}.rollback-discard")
|
||||
if target.exists():
|
||||
if manifest["target_existed"] and inventory(target) == manifest["entries"]:
|
||||
return manifest
|
||||
if not manifest["target_existed"]:
|
||||
raise CheckpointError("ROLLBACK_TARGET_SHOULD_BE_ABSENT")
|
||||
raise CheckpointError("ROLLBACK_TARGET_AMBIGUOUS")
|
||||
if not manifest["target_existed"]:
|
||||
return manifest
|
||||
if not quarantine.exists():
|
||||
raise CheckpointError("ROLLBACK_SOURCE_STATE_MISSING")
|
||||
return restore(checkpoint, target)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
create_parser = sub.add_parser("create")
|
||||
create_parser.add_argument("target", type=Path)
|
||||
create_parser.add_argument("checkpoint", type=Path)
|
||||
create_parser.add_argument("--operation-id", required=True)
|
||||
verify_parser = sub.add_parser("verify")
|
||||
verify_parser.add_argument("checkpoint", type=Path)
|
||||
verify_parser.add_argument("--target", type=Path)
|
||||
restore_parser = sub.add_parser("restore")
|
||||
restore_parser.add_argument("checkpoint", type=Path)
|
||||
restore_parser.add_argument("target", type=Path)
|
||||
recover_parser = sub.add_parser("recover-restore")
|
||||
recover_parser.add_argument("checkpoint", type=Path)
|
||||
recover_parser.add_argument("target", type=Path)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
if args.command == "create":
|
||||
result = create(args.target, args.checkpoint, args.operation_id)
|
||||
elif args.command == "verify":
|
||||
result = verify(args.checkpoint, args.target)
|
||||
elif args.command == "restore":
|
||||
result = restore(args.checkpoint, args.target)
|
||||
else:
|
||||
result = recover_restore(args.checkpoint, args.target)
|
||||
except (CheckpointError, OSError, json.JSONDecodeError) as error:
|
||||
print(json.dumps({"ok": False, "code": str(error)}, sort_keys=True))
|
||||
return 1
|
||||
print(json.dumps({"ok": True, "inventory_sha256": result["inventory_sha256"],
|
||||
"entries": len(result["entries"])}, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -10,6 +10,10 @@ langchain-community
|
||||
llama-index
|
||||
transformers
|
||||
huggingface-hub
|
||||
# Match the approved vLLM 0.10.2 CUDA 12.8 binary baseline.
|
||||
torch==2.8.0
|
||||
torchaudio==2.8.0
|
||||
torchvision==0.23.0
|
||||
peft
|
||||
bitsandbytes
|
||||
accelerate
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"schema": "nexus-one-ai.python-runtime-profiles.v1",
|
||||
"namespace": "offline-workstation/python-runtime-profiles",
|
||||
"architecture": "amd64",
|
||||
"python": "3.11",
|
||||
"selection": "exactly-one",
|
||||
"profiles": {
|
||||
"cpu-only": {
|
||||
"index": "https://download.pytorch.org/whl/cpu",
|
||||
"required": {
|
||||
"torch-2.8.0+cpu-cp311-cp311-manylinux_2_28_x86_64.whl": "cb06175284673a581dd91fb1965662ae4ecaba6e5c357aa0ea7bb8b84b6b7eeb",
|
||||
"torchaudio-2.8.0+cpu-cp311-cp311-manylinux_2_28_x86_64.whl": "e54bd7fc9472019308097d99102df9acee22aa2451ae808d27840bc874320292",
|
||||
"torchvision-0.23.0+cpu-cp311-cp311-manylinux_2_28_x86_64.whl": "d83d8075db43b8ca89680bdeb2f100c832e2a3aa61ee42c038b1a146e5e511b6"
|
||||
},
|
||||
"forbidden_filename_patterns": [
|
||||
"^nvidia_", "cu12", "cu13", "^triton-", "^bitsandbytes-"
|
||||
]
|
||||
},
|
||||
"nvidia-cu128": {
|
||||
"index": "https://download.pytorch.org/whl/cu128",
|
||||
"required": {
|
||||
"torch-2.8.0-cp311-cp311-manylinux_2_28_x86_64.whl": "c12fa219f51a933d5f80eeb3a7a5d0cbe9168c0a14bbb4055f1979431660879b",
|
||||
"torchaudio-2.8.0-cp311-cp311-manylinux_2_28_x86_64.whl": "776c0b4ba84b9e3ddf6304b9c47cd63549d7896a6f3d5184ece074cc3d76ed6b",
|
||||
"torchvision-0.23.0-cp311-cp311-manylinux_2_28_x86_64.whl": "35c27941831b653f5101edfe62c03d196c13f32139310519e8228f35eae0e96a"
|
||||
},
|
||||
"forbidden_filename_patterns": ["cu13"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Recover interrupted Python profile mutation safely after process/reboot restart."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def load_module(name: str, filename: str):
|
||||
spec = importlib.util.spec_from_file_location(name, HERE / filename)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec.loader
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
installer = load_module("boot_profile_installer", "install-python-profile.py")
|
||||
journal_api = load_module("boot_profile_journal", "atomic-profile-journal.py")
|
||||
|
||||
|
||||
def recover(journal: Path, checkpoint: Path, target: Path) -> dict:
|
||||
current = journal_api.read_verified(journal)
|
||||
if current is None:
|
||||
return {"action": "none", "reason": "journal_absent"}
|
||||
state = current["state"]
|
||||
if state in {"installing", "rollback_required"}:
|
||||
result = installer.recover(journal, checkpoint, target)
|
||||
return {"action": "rollback_recovered", **result}
|
||||
if state in {"installed", "rolled_back"}:
|
||||
return {"action": "none", "reason": f"terminal_{state}"}
|
||||
if state in {"profile_selected", "checkpoint_ready"}:
|
||||
return {"action": "none", "reason": f"non_mutating_{state}"}
|
||||
raise ValueError(f"UNKNOWN_JOURNAL_STATE:{state}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--journal", type=Path, required=True)
|
||||
parser.add_argument("--checkpoint", type=Path, required=True)
|
||||
parser.add_argument("--target", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
result = recover(args.journal, args.checkpoint, args.target)
|
||||
except Exception as error:
|
||||
print(json.dumps({"recovered": False, "code": str(error)}, sort_keys=True))
|
||||
return 1
|
||||
print(json.dumps(result, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Resolve an Ubuntu APT dependency closure into candidate payload-lock objects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def filtered_lines(path: Path) -> list[str]:
|
||||
return [
|
||||
line.strip() for line in path.read_text(encoding="utf-8").splitlines()
|
||||
if line.strip() and not line.lstrip().startswith("#")
|
||||
]
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def deb_field(path: Path, field: str) -> str:
|
||||
result = subprocess.run(
|
||||
["dpkg-deb", "-f", str(path), field], check=True, capture_output=True, text=True
|
||||
)
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def resolve(packages: list[str], work: Path) -> list[dict]:
|
||||
archives = work / "archives"
|
||||
archives.mkdir(parents=True, exist_ok=True)
|
||||
(archives / "partial").mkdir(exist_ok=True)
|
||||
status = work / "empty-dpkg-status"
|
||||
status.write_text("", encoding="utf-8")
|
||||
command = [
|
||||
"apt-get", "--download-only", "--yes", "--no-install-recommends",
|
||||
"-o", "Debug::NoLocking=1",
|
||||
"-o", f"Dir::State::status={status}",
|
||||
"-o", f"Dir::Cache::archives={archives}",
|
||||
"-o", "APT::Get::AllowUnauthenticated=false",
|
||||
"install", *packages,
|
||||
]
|
||||
subprocess.run(command, check=True)
|
||||
debs = sorted(archives.glob("*.deb"))
|
||||
if not debs:
|
||||
raise RuntimeError("APT resolution produced no .deb files")
|
||||
objects = []
|
||||
for path in debs:
|
||||
package = deb_field(path, "Package")
|
||||
version = deb_field(path, "Version")
|
||||
architecture = deb_field(path, "Architecture")
|
||||
if architecture not in {"amd64", "all"}:
|
||||
raise RuntimeError(f"unexpected architecture {architecture} for {path.name}")
|
||||
digest = sha256_file(path)
|
||||
objects.append({
|
||||
"id": f"apt-{package}",
|
||||
"version": version,
|
||||
"architecture": architecture,
|
||||
"source": f"apt://ubuntu-22.04/{package}/{version}/{path.name}",
|
||||
"target": f"payload/apt/{path.name}",
|
||||
"sha256": digest,
|
||||
"bytes": path.stat().st_size,
|
||||
"bytes_basis": "measured",
|
||||
"license": "REVIEW-REQUIRED",
|
||||
"requested_directly": package in packages,
|
||||
})
|
||||
return objects
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--lock", type=Path, required=True)
|
||||
parser.add_argument("--packages", type=Path, required=True)
|
||||
parser.add_argument("--work", type=Path, required=True)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
lock = json.loads(args.lock.read_text(encoding="utf-8"))
|
||||
packages = filtered_lines(args.packages)
|
||||
objects = resolve(packages, args.work)
|
||||
lock["objects"]["apt"] = objects
|
||||
lock["unresolved"] = [x for x in lock["unresolved"] if x != "exact APT dependency closure"]
|
||||
lock["unresolved"].extend([
|
||||
"APT repository snapshot/index digests and origin approval",
|
||||
"APT package license mapping and notice extraction",
|
||||
"approved R570/CUDA 12.8.1/cuDNN/NVIDIA Container Toolkit package closure"
|
||||
])
|
||||
lock["unresolved"] = list(dict.fromkeys(lock["unresolved"]))
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(json.dumps(lock, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
print(f"APT requested packages: {len(packages)}")
|
||||
print(f"APT resolved .deb objects: {len(objects)}")
|
||||
print(f"APT measured bytes: {sum(item['bytes'] for item in objects)}")
|
||||
print(f"Wrote candidate lock: {args.output}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Resolve source snapshot and AMD64 OCI metadata into a Gate 2 candidate lock."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import gzip
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tarfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def deterministic_source_archive(source: Path, output: Path) -> dict:
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
excluded_parts = {".git", "__pycache__", ".pytest_cache", "node_modules", ".venv", "venv"}
|
||||
files = sorted(
|
||||
path for path in source.rglob("*")
|
||||
if path.is_file()
|
||||
and not excluded_parts.intersection(path.relative_to(source).parts)
|
||||
and path.suffix != ".iso"
|
||||
and path.name != "how-it-flows.gif"
|
||||
)
|
||||
with output.open("wb") as raw:
|
||||
with gzip.GzipFile(filename="", mode="wb", fileobj=raw, mtime=0) as compressed:
|
||||
with tarfile.open(fileobj=compressed, mode="w") as archive:
|
||||
for path in files:
|
||||
relative = path.relative_to(source)
|
||||
info = archive.gettarinfo(str(path), arcname=str(Path("aipackage") / relative))
|
||||
info.uid = info.gid = 0
|
||||
info.uname = info.gname = "root"
|
||||
info.mtime = 0
|
||||
with path.open("rb") as stream:
|
||||
archive.addfile(info, stream)
|
||||
return {
|
||||
"id": "nexus-one-ai-source-snapshot",
|
||||
"version": os.environ.get("OFFLINE_SOURCE_COMMIT", "exported-snapshot"),
|
||||
"architecture": "all",
|
||||
"source": "local-reviewed-source-snapshot",
|
||||
"target": "payload/source/aipackage.tar.gz",
|
||||
"sha256": sha256_file(output),
|
||||
"bytes": output.stat().st_size,
|
||||
"bytes_basis": "measured",
|
||||
"license": "Proprietary-Cezen-Technologies",
|
||||
"file_count": len(files),
|
||||
}
|
||||
|
||||
|
||||
def manifest_records(reference: str) -> list[dict]:
|
||||
result = subprocess.run(
|
||||
["docker", "manifest", "inspect", "--verbose", reference],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
parsed = json.loads(result.stdout)
|
||||
return parsed if isinstance(parsed, list) else [parsed]
|
||||
|
||||
|
||||
def resolve_container(item: dict) -> dict:
|
||||
records = manifest_records(item["source"])
|
||||
matches = [
|
||||
record for record in records
|
||||
if record.get("Descriptor", {}).get("platform", {}).get("os") == "linux"
|
||||
and record.get("Descriptor", {}).get("platform", {}).get("architecture") == "amd64"
|
||||
]
|
||||
if len(matches) != 1:
|
||||
raise RuntimeError(f"{item['source']}: expected one linux/amd64 manifest, found {len(matches)}")
|
||||
record = matches[0]
|
||||
descriptor = record["Descriptor"]
|
||||
digest = descriptor["digest"]
|
||||
manifest = record.get("SchemaV2Manifest") or {}
|
||||
content = [manifest.get("config", {})] + list(manifest.get("layers", []))
|
||||
declared_bytes = sum(entry.get("size", 0) for entry in content)
|
||||
return {
|
||||
"id": item["id"],
|
||||
"version": item["version"],
|
||||
"architecture": "amd64",
|
||||
"source": f"{item['source'].split(':')[0]}@{digest}",
|
||||
"resolved_from": item["source"],
|
||||
"target": f"payload/containers/{item['id']}.oci.tar",
|
||||
"sha256": digest.removeprefix("sha256:"),
|
||||
"bytes": declared_bytes,
|
||||
"bytes_basis": "registry-declared-content",
|
||||
"license": item["license"],
|
||||
"manifest_bytes": descriptor.get("size"),
|
||||
"config": manifest.get("config"),
|
||||
"layers": manifest.get("layers", []),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--candidate", type=Path, required=True)
|
||||
parser.add_argument("--source-root", type=Path, required=True)
|
||||
parser.add_argument("--container-sources", type=Path, required=True)
|
||||
parser.add_argument("--staging", type=Path, required=True)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
lock = json.loads(args.candidate.read_text(encoding="utf-8"))
|
||||
source_object = deterministic_source_archive(
|
||||
args.source_root.resolve(), args.staging / "source" / "aipackage.tar.gz"
|
||||
)
|
||||
declarations = json.loads(args.container_sources.read_text(encoding="utf-8"))
|
||||
containers = [resolve_container(item) for item in declarations]
|
||||
lock["objects"]["source"] = [source_object]
|
||||
lock["objects"]["containers"] = containers
|
||||
lock["unresolved"] = [
|
||||
item for item in lock["unresolved"]
|
||||
if item not in {"source snapshot archive bytes and digest", "OCI image manifests and layers by digest"}
|
||||
]
|
||||
lock["unresolved"].extend([
|
||||
"OCI archive bytes and SHA-256 after digest-pinned acquisition",
|
||||
"license review for container entries marked REVIEW-REQUIRED",
|
||||
"replace mutable container source tags with approved release tags before final review"
|
||||
])
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(json.dumps(lock, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
print(f"Wrote Gate 2 candidate lock: {args.output}")
|
||||
print(f"Source files: {source_object['file_count']}; source bytes: {source_object['bytes']}")
|
||||
print(f"AMD64 OCI manifests resolved: {len(containers)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Resolve one Python 3.11 wheel closure into candidate payload-lock objects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import email
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def requirements(path: Path) -> list[str]:
|
||||
result = []
|
||||
for raw in path.read_text(encoding="utf-8").splitlines():
|
||||
line = raw.split("#", 1)[0].strip()
|
||||
if line:
|
||||
result.append(line)
|
||||
return result
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def metadata(path: Path) -> email.message.Message:
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
# Some wheels vendor other distributions (for example setuptools). Only the
|
||||
# wheel's root dist-info directory describes the payload being locked.
|
||||
names = [
|
||||
name for name in archive.namelist()
|
||||
if name.endswith(".dist-info/METADATA") and name.count("/") == 1
|
||||
]
|
||||
if len(names) != 1:
|
||||
raise RuntimeError(f"{path.name}: expected one dist-info/METADATA, found {len(names)}")
|
||||
return email.message_from_bytes(archive.read(names[0]))
|
||||
|
||||
|
||||
def normalize(name: str) -> str:
|
||||
return re.sub(r"[-_.]+", "-", name).lower()
|
||||
|
||||
|
||||
def resolve_group(group: str, requested: list[str], work: Path, resolver_pip: str) -> list[dict]:
|
||||
group_work = work / group
|
||||
wheels = group_work / "wheels"
|
||||
wheels.mkdir(parents=True, exist_ok=True)
|
||||
combined = group_work / "requirements.in"
|
||||
combined.write_text("\n".join(dict.fromkeys(requested)) + "\n", encoding="utf-8")
|
||||
|
||||
venv = work / "resolver-venv"
|
||||
if not (venv / "bin/python").exists():
|
||||
subprocess.run(["python3", "-m", "venv", str(venv)], check=True)
|
||||
pip = venv / "bin/pip"
|
||||
subprocess.run([str(pip), "install", "--disable-pip-version-check", f"pip=={resolver_pip}"], check=True)
|
||||
command = [
|
||||
str(pip), "download", "--disable-pip-version-check", "--only-binary=:all:",
|
||||
"--python-version", "3.11", "--implementation", "cp", "--abi", "cp311",
|
||||
"--dest", str(wheels), "--requirement", str(combined),
|
||||
]
|
||||
subprocess.run(command, check=True)
|
||||
|
||||
objects = []
|
||||
versions: dict[str, str] = {}
|
||||
for path in sorted(wheels.glob("*.whl")):
|
||||
meta = metadata(path)
|
||||
name = meta.get("Name")
|
||||
version = meta.get("Version")
|
||||
if not name or not version:
|
||||
raise RuntimeError(f"{path.name}: missing Name or Version metadata")
|
||||
key = normalize(name)
|
||||
previous = versions.setdefault(key, version)
|
||||
if previous != version:
|
||||
raise RuntimeError(f"multiple versions resolved for {name}: {previous}, {version}")
|
||||
license_value = str(
|
||||
meta.get("License-Expression") or meta.get("License") or "REVIEW-REQUIRED"
|
||||
)
|
||||
if len(license_value) > 160 or "\n" in license_value or not license_value.strip():
|
||||
license_value = "REVIEW-REQUIRED"
|
||||
architecture = "all" if path.name.endswith("-none-any.whl") else "amd64"
|
||||
objects.append({
|
||||
"id": f"python-{group}-{key}",
|
||||
"version": version,
|
||||
"architecture": architecture,
|
||||
"source": f"pypi://{key}/{version}/{path.name}",
|
||||
"target": f"payload/python/{group}/{path.name}",
|
||||
"sha256": sha256_file(path),
|
||||
"bytes": path.stat().st_size,
|
||||
"bytes_basis": "measured",
|
||||
"license": license_value.strip(),
|
||||
})
|
||||
if not objects:
|
||||
raise RuntimeError(f"Python resolution produced no wheels for {group}")
|
||||
return objects
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--lock", type=Path, required=True)
|
||||
parser.add_argument("--backend-requirements", type=Path, required=True)
|
||||
parser.add_argument("--platform-requirements", type=Path, required=True)
|
||||
parser.add_argument("--work", type=Path, required=True)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--resolver-pip", default="25.1.1")
|
||||
args = parser.parse_args()
|
||||
|
||||
lock = json.loads(args.lock.read_text(encoding="utf-8"))
|
||||
platform_requested = requirements(args.platform_requirements)
|
||||
chromadb_requested = [item for item in platform_requested if normalize(item.split("=", 1)[0]) == "chromadb"]
|
||||
remaining_platform = [item for item in platform_requested if item not in chromadb_requested]
|
||||
groups = {
|
||||
"backend": requirements(args.backend_requirements),
|
||||
"chromadb": chromadb_requested,
|
||||
"platform": remaining_platform,
|
||||
}
|
||||
if not groups["chromadb"]:
|
||||
raise RuntimeError("platform requirements do not declare the ChromaDB wheelhouse")
|
||||
objects = []
|
||||
for group, requested in groups.items():
|
||||
objects.extend(resolve_group(group, requested, args.work, args.resolver_pip))
|
||||
lock["objects"]["python"] = objects
|
||||
lock["unresolved"] = [x for x in lock["unresolved"] if x != "hash-locked Python dependency closure"]
|
||||
lock["unresolved"].extend([
|
||||
"Python index snapshot/origin and resolver report approval",
|
||||
"Python wheel license/notice review",
|
||||
"Python 3.11 clean-environment import and offline installation evidence",
|
||||
"GPU framework compatibility with approved CUDA 12.8.1 runtime"
|
||||
])
|
||||
lock["unresolved"] = list(dict.fromkeys(lock["unresolved"]))
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(json.dumps(lock, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
print(f"Python wheel objects: {len(objects)}")
|
||||
for group in groups:
|
||||
print(f"Python {group} wheel objects: {sum(item['id'].startswith(f'python-{group}-') for item in objects)}")
|
||||
print(f"Python measured bytes: {sum(item['bytes'] for item in objects)}")
|
||||
print(f"License review required: {sum(item['license'] == 'REVIEW-REQUIRED' for item in objects)}")
|
||||
print(f"Wrote candidate lock: {args.output}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail-closed validator for the Offline Workstation Gate 2 payload lock."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
||||
IMMUTABLE_IMAGE = re.compile(r"^[^\s]+@sha256:([0-9a-f]{64})$")
|
||||
FORBIDDEN_SOURCE = re.compile(r"(^|[/_.:-])(latest|main|master)([/_.:@-]|$)", re.I)
|
||||
REQUIRED_OBJECT_FIELDS = {"id", "version", "architecture", "source", "sha256", "bytes", "license"}
|
||||
OBJECT_GROUPS = ("apt", "python", "runtime", "containers", "models", "source")
|
||||
|
||||
|
||||
def fail(errors: list[str], message: str) -> None:
|
||||
errors.append(message)
|
||||
|
||||
|
||||
def validate(path: Path) -> list[str]:
|
||||
errors: list[str] = []
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
except FileNotFoundError:
|
||||
return [f"payload lock does not exist: {path}"]
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
return [f"payload lock is unreadable or invalid JSON: {exc}"]
|
||||
|
||||
if data.get("schema") != "nexus-one-ai.offline-workstation-payload-lock.v1":
|
||||
fail(errors, "schema must be nexus-one-ai.offline-workstation-payload-lock.v1")
|
||||
if data.get("product") != "offline-workstation" or data.get("architecture") != "amd64":
|
||||
fail(errors, "product/architecture must be offline-workstation/amd64")
|
||||
if data.get("ubuntu_release") != "22.04.5":
|
||||
fail(errors, "ubuntu_release must be 22.04.5")
|
||||
if data.get("approval_record") != "CT-G1-2026-009":
|
||||
fail(errors, "approval_record must bind the approved payload boundary CT-G1-2026-009")
|
||||
if data.get("status") != "reviewed":
|
||||
fail(errors, "status must be reviewed before acquisition or assembly")
|
||||
if data.get("unresolved"):
|
||||
fail(errors, "unresolved must be an empty array")
|
||||
|
||||
objects = data.get("objects")
|
||||
if not isinstance(objects, dict):
|
||||
return errors + ["objects must be an object"]
|
||||
|
||||
seen_ids: set[str] = set()
|
||||
seen_paths: set[str] = set()
|
||||
for group in OBJECT_GROUPS:
|
||||
entries = objects.get(group)
|
||||
if not isinstance(entries, list) or not entries:
|
||||
fail(errors, f"objects.{group} must be a non-empty array")
|
||||
continue
|
||||
for index, entry in enumerate(entries):
|
||||
label = f"objects.{group}[{index}]"
|
||||
if not isinstance(entry, dict):
|
||||
fail(errors, f"{label} must be an object")
|
||||
continue
|
||||
missing = sorted(REQUIRED_OBJECT_FIELDS - entry.keys())
|
||||
if missing:
|
||||
fail(errors, f"{label} missing fields: {', '.join(missing)}")
|
||||
continue
|
||||
object_id = entry["id"]
|
||||
if not isinstance(object_id, str) or not object_id:
|
||||
fail(errors, f"{label}.id must be non-empty")
|
||||
elif object_id in seen_ids:
|
||||
fail(errors, f"duplicate object id: {object_id}")
|
||||
else:
|
||||
seen_ids.add(object_id)
|
||||
if entry.get("architecture") not in {"amd64", "all"}:
|
||||
fail(errors, f"{label}.architecture must be amd64 or all")
|
||||
if not isinstance(entry.get("bytes"), int) or entry["bytes"] <= 0:
|
||||
fail(errors, f"{label}.bytes must be a positive integer")
|
||||
if data.get("status") == "reviewed" and entry.get("bytes_basis") != "measured":
|
||||
fail(errors, f"{label}.bytes_basis must be measured for a reviewed lock")
|
||||
if not isinstance(entry.get("sha256"), str) or not SHA256.fullmatch(entry["sha256"]):
|
||||
fail(errors, f"{label}.sha256 must be 64 lowercase hexadecimal characters")
|
||||
for field in ("version", "source", "license"):
|
||||
value = entry.get(field)
|
||||
if not isinstance(value, str) or not value.strip():
|
||||
fail(errors, f"{label}.{field} must be non-empty")
|
||||
source = entry.get("source", "")
|
||||
version = entry.get("version", "")
|
||||
if FORBIDDEN_SOURCE.search(source) or FORBIDDEN_SOURCE.search(version):
|
||||
fail(errors, f"{label} contains mutable latest/main/master identity")
|
||||
target = entry.get("target")
|
||||
if not isinstance(target, str) or not target or target.startswith("/") or ".." in Path(target).parts:
|
||||
fail(errors, f"{label}.target must be a safe relative path")
|
||||
elif target in seen_paths:
|
||||
fail(errors, f"duplicate target path: {target}")
|
||||
else:
|
||||
seen_paths.add(target)
|
||||
if group == "containers":
|
||||
match = IMMUTABLE_IMAGE.fullmatch(source)
|
||||
if not match:
|
||||
fail(errors, f"{label}.source must be an OCI reference pinned with @sha256")
|
||||
elif match.group(1) != entry["sha256"]:
|
||||
fail(errors, f"{label} OCI digest must match sha256")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 2:
|
||||
print(f"usage: {Path(sys.argv[0]).name} PAYLOAD_LOCK.json", file=sys.stderr)
|
||||
return 2
|
||||
errors = validate(Path(sys.argv[1]))
|
||||
if errors:
|
||||
for error in errors:
|
||||
print(f"ERROR: {error}", file=sys.stderr)
|
||||
return 1
|
||||
print("Offline Workstation payload lock preflight passed.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail closed unless a selected Python wheel directory obeys its runtime profile."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def validate(contract: dict, profile_name: str, wheel_dir: Path) -> list[str]:
|
||||
errors: list[str] = []
|
||||
profiles = contract.get("profiles", {})
|
||||
if profile_name not in profiles:
|
||||
return [f"unknown profile: {profile_name}"]
|
||||
profile = profiles[profile_name]
|
||||
wheels = {path.name: path for path in wheel_dir.glob("*.whl") if path.is_file()}
|
||||
if not wheels:
|
||||
errors.append("selected wheel directory is empty")
|
||||
for pattern in profile.get("forbidden_filename_patterns", []):
|
||||
matches = sorted(name for name in wheels if re.search(pattern, name, re.IGNORECASE))
|
||||
if matches:
|
||||
errors.append(f"forbidden pattern {pattern!r}: {', '.join(matches)}")
|
||||
for name, expected in profile.get("required", {}).items():
|
||||
path = wheels.get(name)
|
||||
if path is None:
|
||||
errors.append(f"missing required wheel: {name}")
|
||||
elif sha256(path) != expected:
|
||||
errors.append(f"digest mismatch: {name}")
|
||||
return errors
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("contract", type=Path)
|
||||
parser.add_argument("profile")
|
||||
parser.add_argument("wheel_dir", type=Path)
|
||||
args = parser.parse_args()
|
||||
contract = json.loads(args.contract.read_text(encoding="utf-8"))
|
||||
errors = validate(contract, args.profile, args.wheel_dir)
|
||||
if errors:
|
||||
for error in errors:
|
||||
print(f"ERROR: {error}")
|
||||
return 1
|
||||
print(f"profile valid: {args.profile}; wheels={len(list(args.wheel_dir.glob('*.whl')))}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify signed Python profile membership and emit a bound verification receipt."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class VerificationError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def canonical(value: dict) -> bytes:
|
||||
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()
|
||||
|
||||
|
||||
def sha256(data: bytes) -> str:
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def verify(manifest: dict, envelope: dict, trust: dict, public_key: Path,
|
||||
environment: str, verified_at: str, verifier_build_sha256: str) -> dict:
|
||||
body = canonical(manifest)
|
||||
manifest_digest = sha256(body)
|
||||
if manifest.get("namespace") != "offline-workstation/python-runtime-profiles":
|
||||
raise VerificationError("NAMESPACE_INVALID")
|
||||
if envelope.get("schema") != "com.cezen.nexus-one.signature-envelope.v1" \
|
||||
or envelope.get("algorithm") != "ed25519":
|
||||
raise VerificationError("ENVELOPE_INVALID")
|
||||
if envelope.get("manifest_digest") != f"sha256:{manifest_digest}":
|
||||
raise VerificationError("MANIFEST_DIGEST_MISMATCH")
|
||||
if envelope.get("policy_version") != trust.get("policy_version"):
|
||||
raise VerificationError("POLICY_VERSION_MISMATCH")
|
||||
keys = [item for item in trust.get("keys", []) if item.get("key_id") == envelope.get("key_id")]
|
||||
if len(keys) != 1:
|
||||
raise VerificationError("KEY_UNKNOWN_OR_AMBIGUOUS")
|
||||
key = keys[0]
|
||||
required = {"algorithm": "ed25519", "purpose": "profile-membership", "status": "active",
|
||||
"environment": environment}
|
||||
if any(key.get(name) != value for name, value in required.items()):
|
||||
raise VerificationError("KEY_POLICY_REJECTED")
|
||||
der = subprocess.run(
|
||||
["openssl", "pkey", "-pubin", "-in", str(public_key), "-outform", "DER"],
|
||||
check=True, capture_output=True,
|
||||
).stdout
|
||||
fingerprint = sha256(der)
|
||||
if fingerprint != key.get("public_key_sha256"):
|
||||
raise VerificationError("KEY_FINGERPRINT_MISMATCH")
|
||||
try:
|
||||
signature = base64.b64decode(envelope.get("signature", ""), validate=True)
|
||||
except ValueError as error:
|
||||
raise VerificationError("SIGNATURE_ENCODING_INVALID") from error
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
body_path = Path(directory) / "manifest.canonical.json"
|
||||
signature_path = Path(directory) / "signature.bin"
|
||||
body_path.write_bytes(body)
|
||||
signature_path.write_bytes(signature)
|
||||
result = subprocess.run([
|
||||
"openssl", "pkeyutl", "-verify", "-pubin", "-inkey", str(public_key),
|
||||
"-rawin", "-in", str(body_path), "-sigfile", str(signature_path),
|
||||
], capture_output=True)
|
||||
if result.returncode != 0:
|
||||
raise VerificationError("SIGNATURE_INVALID")
|
||||
return {
|
||||
"schema": "com.cezen.nexus-one.profile-membership-verification-receipt.v1",
|
||||
"environment": environment,
|
||||
"result": "verified",
|
||||
"manifest_sha256": manifest_digest,
|
||||
"envelope_sha256": sha256(canonical(envelope)),
|
||||
"key_id": envelope["key_id"],
|
||||
"key_fingerprint_sha256": fingerprint,
|
||||
"trust_snapshot_sha256": sha256(canonical(trust)),
|
||||
"policy_version": trust["policy_version"],
|
||||
"namespace": manifest["namespace"],
|
||||
"profiles": sorted(manifest["profiles"]),
|
||||
"verified_at": verified_at,
|
||||
"verifier_build_sha256": verifier_build_sha256,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--manifest", type=Path, required=True)
|
||||
parser.add_argument("--envelope", type=Path, required=True)
|
||||
parser.add_argument("--trust-snapshot", type=Path, required=True)
|
||||
parser.add_argument("--public-key", type=Path, required=True)
|
||||
parser.add_argument("--environment", choices=("production", "test"), required=True)
|
||||
parser.add_argument("--verified-at", required=True)
|
||||
args = parser.parse_args()
|
||||
script_digest = sha256(Path(__file__).read_bytes())
|
||||
try:
|
||||
receipt = verify(
|
||||
json.loads(args.manifest.read_text()), json.loads(args.envelope.read_text()),
|
||||
json.loads(args.trust_snapshot.read_text()), args.public_key,
|
||||
args.environment, args.verified_at, script_digest,
|
||||
)
|
||||
except (VerificationError, subprocess.CalledProcessError, json.JSONDecodeError) as error:
|
||||
print(json.dumps({"result": "rejected", "code": str(error)}, sort_keys=True))
|
||||
return 1
|
||||
print(json.dumps(receipt, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user