From 04951e75c998d82f701072005bbdfcfb92da552f Mon Sep 17 00:00:00 2001 From: Yuqiang Wang <10815391+schchit@users.noreply.github.com> Date: Sun, 13 Sep 2026 09:30:13 +0800 Subject: [PATCH] Require acceptance audience and return failure exit codes --- HARDENING.md | 3 +++ README.md | 4 ++++ jep_cli/main.py | 11 ++++++++--- tests/test_acceptance.py | 16 ++++++++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 HARDENING.md create mode 100644 tests/test_acceptance.py diff --git a/HARDENING.md b/HARDENING.md new file mode 100644 index 0000000..7906354 --- /dev/null +++ b/HARDENING.md @@ -0,0 +1,3 @@ +# Acceptance context and exit status + +`jep verify --mode acceptance` requires `--expected-audience`. The value is forwarded to the API and negative verification exits with status 2. Malformed input/transport errors exit with status 1. `--ref` accepts either a digest or a JSON reference descriptor. All verification assurance comes from the configured API; the CLI does not perform local cryptographic verification. diff --git a/README.md b/README.md index b9127ca..7a13d16 100644 --- a/README.md +++ b/README.md @@ -114,3 +114,7 @@ jep verify event.json ## License MIT + +## Runtime and verification notes + +See [HARDENING.md](HARDENING.md) for supported behavior, regression checks, and compatibility boundaries. diff --git a/jep_cli/main.py b/jep_cli/main.py index fd2d71b..ddd1347 100644 --- a/jep_cli/main.py +++ b/jep_cli/main.py @@ -62,7 +62,7 @@ def cmd_create(args: argparse.Namespace) -> int: if args.aud: payload["aud"] = args.aud if args.ref: - payload["ref"] = args.ref + payload["ref"] = parse_json_value(args.ref) if args.ttl_minutes is not None: payload["ttl_minutes"] = args.ttl_minutes if args.digest_only_who: @@ -86,9 +86,13 @@ def cmd_verify(args: argparse.Namespace) -> int: "mode": args.mode, "consume_nonce": args.consume_nonce, } + if args.expected_audience: + payload["expected_audience"] = args.expected_audience + if args.mode == "acceptance" and not args.expected_audience: + raise ValueError("acceptance mode requires --expected-audience") result = build_client(args).verify_event(payload) print_json(result) - return 0 + return 0 if result.get("valid") is True else 2 def cmd_health(args: argparse.Namespace) -> int: @@ -130,7 +134,8 @@ def build_parser() -> argparse.ArgumentParser: verify = sub.add_parser("verify", help="Verify a JEP event") verify.add_argument("event", help="Event JSON object, event JSON file path, or '-' for stdin") - verify.add_argument("--mode", default="archival", help="Validation mode") + verify.add_argument("--mode", default="archival", choices=["archival", "acceptance"], help="Validation mode") + verify.add_argument("--expected-audience", default="", help="Required receiving audience for acceptance") verify.add_argument("--consume-nonce", action="store_true", help="Ask verifier to consume nonce") verify.set_defaults(func=cmd_verify) diff --git a/tests/test_acceptance.py b/tests/test_acceptance.py new file mode 100644 index 0000000..ecdfa95 --- /dev/null +++ b/tests/test_acceptance.py @@ -0,0 +1,16 @@ +import json +from jep_cli import main as module + + +def test_acceptance_requires_expected_audience(capsys): + assert module.main(["verify", '{"jep":"1"}', "--mode", "acceptance"]) == 1 + assert "expected-audience" in capsys.readouterr().err + + +def test_verification_failure_returns_nonzero(monkeypatch): + class Client: + def verify_event(self, payload): + assert payload["expected_audience"] == "receiver" + return {"valid": False} + monkeypatch.setattr(module, "build_client", lambda args: Client()) + assert module.main(["verify", '{}', "--mode", "acceptance", "--expected-audience", "receiver"]) == 2