Allow compatible CLI release upgrades

This commit is contained in:
HumbleTurtle
2026-08-01 04:15:09 -03:00
parent 0e552497b2
commit 39adc19510
2 changed files with 64 additions and 11 deletions
+33 -11
View File
@@ -3,15 +3,17 @@
Usage: Usage:
python3 nap_install.py python3 nap_install.py
nap init
nap status
nap doctor
nap auth login
nap project create nap project create
nap project status nap project attach
nap mcp status
nap gateway start nap gateway start
nap gateway stop nap gateway stop
nap gateway configure
nap gateway logs
nap status
nap agent list
nap update nap update
nap version
""" """
import ast import ast
@@ -212,6 +214,23 @@ def bundle_manifest():
return fetch_json("/v1/scripts", "CLI bundle manifest") return fetch_json("/v1/scripts", "CLI bundle manifest")
def contract_version_tuple(value):
parts = str(value or "").split("-")
if len(parts) != 3 or any(not part.isdigit() for part in parts):
return None
year, month, day = (int(part) for part in parts)
if year < 2000 or not 1 <= month <= 12 or not 1 <= day <= 31:
return None
return year, month, day
def release_version_tuple(value):
parts = str(value or "").split(".")
if len(parts) != 3 or any(not part.isdigit() for part in parts):
return None
return tuple(int(part) for part in parts)
def validated_manifest_items(manifest): def validated_manifest_items(manifest):
if manifest.get("schema_version") != CLI_BUNDLE_SCHEMA_VERSION: if manifest.get("schema_version") != CLI_BUNDLE_SCHEMA_VERSION:
raise RuntimeError( raise RuntimeError(
@@ -220,12 +239,15 @@ def validated_manifest_items(manifest):
contract = manifest.get("contract") contract = manifest.get("contract")
if not isinstance(contract, dict): if not isinstance(contract, dict):
raise RuntimeError("CLI bundle manifest is missing contract metadata") raise RuntimeError("CLI bundle manifest is missing contract metadata")
if contract.get("current") != CLI_DISTRIBUTION_CONTRACT_VERSION: current_contract = contract_version_tuple(contract.get("current"))
raise RuntimeError("CLI bundle contract does not match this installer") minimum_contract = contract_version_tuple(contract.get("minimum_supported"))
if contract.get("minimum_supported") != CLI_MINIMUM_CONTRACT_VERSION: installer_contract = contract_version_tuple(CLI_DISTRIBUTION_CONTRACT_VERSION)
raise RuntimeError("CLI bundle minimum contract metadata is inconsistent") if current_contract is None or minimum_contract is None or current_contract < minimum_contract:
if manifest.get("release_version") != CLI_RELEASE_VERSION: raise RuntimeError("CLI bundle contract metadata is invalid")
raise RuntimeError("CLI bundle release version does not match this installer") if installer_contract is None or installer_contract < minimum_contract:
raise RuntimeError("CLI bundle requires a newer bootstrap installer")
if release_version_tuple(manifest.get("release_version")) is None:
raise RuntimeError("CLI bundle release version is invalid")
source = manifest.get("source") source = manifest.get("source")
if ( if (
not isinstance(source, dict) not isinstance(source, dict)
+31
View File
@@ -282,3 +282,34 @@ def test_manifest_rejects_path_traversal():
assert "unsafe install_path" in str(exc) assert "unsafe install_path" in str(exc)
else: else:
raise AssertionError("unsafe manifest path must be rejected") raise AssertionError("unsafe manifest path must be rejected")
def test_manifest_accepts_a_future_compatible_release():
module = load_installer()
manifest, _payloads = fake_bundle(module)
manifest["release_version"] = "0.2.2"
manifest["contract"]["current"] = "2026-08-02"
for item in manifest["items"]:
item["version"] = "0.2.2"
item["contract_version"] = "2026-08-02"
items = module.validated_manifest_items(manifest)
assert len(items) == len(module.SCRIPT_NAMES)
def test_manifest_rejects_a_minimum_contract_newer_than_installer():
module = load_installer()
manifest, _payloads = fake_bundle(module)
manifest["contract"]["current"] = "2026-08-02"
manifest["contract"]["minimum_supported"] = "2026-08-02"
for item in manifest["items"]:
item["contract_version"] = "2026-08-02"
item["minimum_contract_version"] = "2026-08-02"
try:
module.validated_manifest_items(manifest)
except RuntimeError as exc:
assert "newer bootstrap installer" in str(exc)
else:
raise AssertionError("incompatible minimum contract must be rejected")