src/ail_ref/cli.py
Read-only source from the unchanged 0.3.4 kit. Existing license terms apply.
from __future__ import annotations
import argparse
import json
from pathlib import Path
from .anchor import compare_public_anchor, verify_with_trusted_https_anchor
from .canonical import canonicalize, pretty
from .parser import ParseError, parse
from .packet import load_and_verify_packet, verify_trusted_fingerprint
from .passport import inspect_passport
from .registry import load_registry
from .validator import validate
def read_document(path: str):
return parse(Path(path).read_text(encoding="utf-8"))
def command_validate(args) -> int:
try:
document = read_document(args.file)
except (OSError, ParseError) as exc:
print(json.dumps({"valid": False, "issues": [{"code": "E01", "message": str(exc)}]}, indent=2))
return 1
try:
registry = load_registry(args.registry) if args.registry else None
except (OSError, ValueError) as exc:
print(json.dumps({"valid": False, "issues": [{"code": "E03", "message": str(exc)}]}, indent=2))
return 1
issues = validate(document, strict_registry=args.strict_registry, registry=registry)
print(json.dumps({"valid": not issues, "issues": [issue.__dict__ for issue in issues]}, indent=2))
return 0 if not issues else 1
def command_normalize(args) -> int:
try:
document = read_document(args.file)
except (OSError, ParseError) as exc:
print(f"E01 {exc}")
return 1
print(pretty(document) if args.pretty else canonicalize(document))
return 0
def command_passport(args) -> int:
try:
result = inspect_passport(read_document(args.file))
except (OSError, ParseError, ValueError) as exc:
print(json.dumps({"status": "ail:rejected", "reason": str(exc)}, indent=2))
return 1
print(json.dumps(result.__dict__, indent=2))
return 0 if result.status != "ail:rejected" else 1
def command_verify_packet(args) -> int:
try:
if args.trusted_fingerprint:
if args.trusted_anchor_url or args.anchor_file:
raise ValueError("Use fingerprint verification or anchor verification in separate calls")
packet = json.loads(Path(args.file).read_text(encoding="utf-8"))
result = verify_trusted_fingerprint(packet, args.trusted_fingerprint)
elif args.trusted_anchor_url:
packet = json.loads(Path(args.file).read_text(encoding="utf-8"))
if args.anchor_file:
anchor = json.loads(Path(args.anchor_file).read_text(encoding="utf-8"))
result = compare_public_anchor(packet, anchor, args.trusted_anchor_url)
else:
result = verify_with_trusted_https_anchor(packet, args.trusted_anchor_url)
elif args.anchor_file:
raise ValueError("--anchor-file requires --trusted-anchor-url")
else:
result = load_and_verify_packet(args.file)
except (OSError, ValueError, json.JSONDecodeError) as exc:
print(json.dumps({"valid": False, "signature_status": "ail:rejected", "reason": str(exc)}, indent=2))
return 1
data = result if isinstance(result, dict) else result.__dict__
print(json.dumps(data, indent=2))
return 0 if data["valid"] else 1
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="ail", description="AIL 0.2 reference interpreter")
subparsers = parser.add_subparsers(dest="command", required=True)
validate_parser = subparsers.add_parser("validate", help="parse and validate an AIL document")
validate_parser.add_argument("file")
validate_parser.add_argument("--strict-registry", action="store_true")
validate_parser.add_argument("--registry", help="path to a registry JSON file")
validate_parser.set_defaults(func=command_validate)
normalize_parser = subparsers.add_parser("normalize", help="emit canonical AIL text")
normalize_parser.add_argument("file")
normalize_parser.add_argument("--pretty", action="store_true")
normalize_parser.set_defaults(func=command_normalize)
passport_parser = subparsers.add_parser("passport", help="evaluate an AIL passport")
passport_parser.add_argument("file")
passport_parser.set_defaults(func=command_passport)
packet_parser = subparsers.add_parser("verify-packet", help="verify an AIL 0.3 JSON identity packet")
packet_parser.add_argument("file")
packet_parser.add_argument("--trusted-anchor-url", help="independently selected HTTPS anchor URL; fetches it unless --anchor-file is given")
packet_parser.add_argument("--trusted-fingerprint", help="expected sha256: fingerprint obtained independently; mismatch exits with failure")
packet_parser.add_argument("--anchor-file", help="compare a supplied anchor file only; this does not prove HTTPS provenance")
packet_parser.set_defaults(func=command_verify_packet)
return parser
def main(argv=None) -> int:
args = build_parser().parse_args(argv)
return args.func(args)
if __name__ == "__main__":
raise SystemExit(main())