jep-cli 0.6.1__tar.gz → 0.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jep_cli-0.6.1 → jep_cli-0.7.2}/NOTICE.md +1 -1
- jep_cli-0.7.2/PKG-INFO +124 -0
- jep_cli-0.7.2/README.md +102 -0
- jep_cli-0.7.2/jep_cli/__init__.py +3 -0
- {jep_cli-0.6.1 → jep_cli-0.7.2}/jep_cli/client.py +15 -7
- {jep_cli-0.6.1 → jep_cli-0.7.2}/jep_cli/main.py +44 -23
- jep_cli-0.7.2/jep_cli/strict_json.py +78 -0
- jep_cli-0.7.2/jep_cli.egg-info/PKG-INFO +124 -0
- {jep_cli-0.6.1 → jep_cli-0.7.2}/jep_cli.egg-info/SOURCES.txt +3 -1
- {jep_cli-0.6.1 → jep_cli-0.7.2}/jep_cli.egg-info/requires.txt +1 -0
- {jep_cli-0.6.1 → jep_cli-0.7.2}/pyproject.toml +3 -3
- jep_cli-0.7.2/tests/test_acceptance.py +40 -0
- {jep_cli-0.6.1 → jep_cli-0.7.2}/tests/test_cli.py +44 -22
- jep_cli-0.7.2/tests/test_strict_json.py +87 -0
- jep_cli-0.6.1/PKG-INFO +0 -141
- jep_cli-0.6.1/README.md +0 -120
- jep_cli-0.6.1/jep_cli/__init__.py +0 -3
- jep_cli-0.6.1/jep_cli.egg-info/PKG-INFO +0 -141
- jep_cli-0.6.1/tests/test_acceptance.py +0 -16
- {jep_cli-0.6.1 → jep_cli-0.7.2}/LICENSE +0 -0
- {jep_cli-0.6.1 → jep_cli-0.7.2}/jep_cli.egg-info/dependency_links.txt +0 -0
- {jep_cli-0.6.1 → jep_cli-0.7.2}/jep_cli.egg-info/entry_points.txt +0 -0
- {jep_cli-0.6.1 → jep_cli-0.7.2}/jep_cli.egg-info/top_level.txt +0 -0
- {jep_cli-0.6.1 → jep_cli-0.7.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Notice
|
|
2
2
|
|
|
3
|
-
This repository contains an experimental CLI seed for the JEP
|
|
3
|
+
This repository contains an experimental CLI seed for the JEP reference API. Supported protocol revisions are documented in README.md.
|
|
4
4
|
|
|
5
5
|
Internet-Draft text and excerpts are governed by the IETF Trust Legal Provisions and BCP 78 / BCP 79.
|
|
6
6
|
|
jep_cli-0.7.2/PKG-INFO
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jep-cli
|
|
3
|
+
Version: 0.7.2
|
|
4
|
+
Summary: Command-line client for JEP Core 0.7 with explicit legacy compatibility
|
|
5
|
+
Author-email: HJS Spec <signal@humanjudgment.org>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/hjs-spec/cli
|
|
8
|
+
Project-URL: Repository, https://github.com/hjs-spec/cli
|
|
9
|
+
Project-URL: Issues, https://github.com/hjs-spec/cli/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
License-File: NOTICE.md
|
|
18
|
+
Requires-Dist: rfc8785==0.1.4
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# JEP CLI — JEP Core 0.7
|
|
24
|
+
|
|
25
|
+
Command-line client for creating and verifying current JEP Core 0.7 events
|
|
26
|
+
through the JEP API.
|
|
27
|
+
|
|
28
|
+
Default endpoints:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
POST /v0.7/events/create
|
|
32
|
+
POST /v0.7/events/verify
|
|
33
|
+
GET /health
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Historical pre-0.7 handling is explicit through `--legacy`; the CLI never
|
|
37
|
+
retries a failed 0.7 event with a legacy decoder.
|
|
38
|
+
|
|
39
|
+
## Status
|
|
40
|
+
|
|
41
|
+
Experimental reference client. It does not define Core semantics or determine
|
|
42
|
+
truth, authorization validity, legal effect, causality, or policy outcome.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install jep-cli
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export JEP_API_URL=http://127.0.0.1:8000
|
|
54
|
+
export JEP_API_KEY=
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Create
|
|
58
|
+
|
|
59
|
+
Judgment:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
jep create \
|
|
63
|
+
--verb J \
|
|
64
|
+
--who did:example:agent-789 \
|
|
65
|
+
--what '{"claim":"approve","subject":"demo"}'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Delegation:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
jep create \
|
|
72
|
+
--verb D \
|
|
73
|
+
--who did:example:principal \
|
|
74
|
+
--what '{"delegatee":"did:example:agent","scope":{"actions":["read"]}}'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Termination and Verification require `--ref`. Pass a typed reference as
|
|
78
|
+
JSON when the target is another JEP event.
|
|
79
|
+
|
|
80
|
+
## Verify
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
jep verify event.json --mode archival
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
0.7 validation returns `status` plus independent `checks`, not a cumulative
|
|
87
|
+
Validation Level.
|
|
88
|
+
|
|
89
|
+
## Explicit legacy verification
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
jep verify old-event.json \
|
|
93
|
+
--legacy \
|
|
94
|
+
--legacy-format json-sorted-v1
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Legacy selection must come from known artifact context. Do not use a failed 0.7
|
|
98
|
+
validation as evidence that an event should be treated as 0.6.
|
|
99
|
+
|
|
100
|
+
## JEP Core 0.7 model
|
|
101
|
+
|
|
102
|
+
- Event Identity is `(who,id)`.
|
|
103
|
+
- Event Hash identifies an exact signed artifact.
|
|
104
|
+
- Core does not require a top-level nonce.
|
|
105
|
+
- D requires `delegatee + scope`.
|
|
106
|
+
- T requires target `ref + termination_scope`.
|
|
107
|
+
- V requires target `ref + verification_scope + result`.
|
|
108
|
+
- chain and policy semantics remain outside Core.
|
|
109
|
+
|
|
110
|
+
## Related repositories
|
|
111
|
+
|
|
112
|
+
- JEP Core: https://github.com/hjs-spec/jep-core
|
|
113
|
+
- JEP API: https://github.com/hjs-spec/jep-api
|
|
114
|
+
- Python SDK: https://github.com/hjs-spec/sdk-py
|
|
115
|
+
- JavaScript SDK: https://github.com/hjs-spec/sdk-js
|
|
116
|
+
- Go SDK: https://github.com/hjs-spec/sdk-go
|
|
117
|
+
|
|
118
|
+
## Public draft
|
|
119
|
+
|
|
120
|
+
- https://datatracker.ietf.org/doc/draft-wang-jep-judgment-event-protocol/
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
jep_cli-0.7.2/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# JEP CLI — JEP Core 0.7
|
|
2
|
+
|
|
3
|
+
Command-line client for creating and verifying current JEP Core 0.7 events
|
|
4
|
+
through the JEP API.
|
|
5
|
+
|
|
6
|
+
Default endpoints:
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
POST /v0.7/events/create
|
|
10
|
+
POST /v0.7/events/verify
|
|
11
|
+
GET /health
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Historical pre-0.7 handling is explicit through `--legacy`; the CLI never
|
|
15
|
+
retries a failed 0.7 event with a legacy decoder.
|
|
16
|
+
|
|
17
|
+
## Status
|
|
18
|
+
|
|
19
|
+
Experimental reference client. It does not define Core semantics or determine
|
|
20
|
+
truth, authorization validity, legal effect, causality, or policy outcome.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install jep-cli
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
export JEP_API_URL=http://127.0.0.1:8000
|
|
32
|
+
export JEP_API_KEY=
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Create
|
|
36
|
+
|
|
37
|
+
Judgment:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
jep create \
|
|
41
|
+
--verb J \
|
|
42
|
+
--who did:example:agent-789 \
|
|
43
|
+
--what '{"claim":"approve","subject":"demo"}'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Delegation:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
jep create \
|
|
50
|
+
--verb D \
|
|
51
|
+
--who did:example:principal \
|
|
52
|
+
--what '{"delegatee":"did:example:agent","scope":{"actions":["read"]}}'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Termination and Verification require `--ref`. Pass a typed reference as
|
|
56
|
+
JSON when the target is another JEP event.
|
|
57
|
+
|
|
58
|
+
## Verify
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
jep verify event.json --mode archival
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
0.7 validation returns `status` plus independent `checks`, not a cumulative
|
|
65
|
+
Validation Level.
|
|
66
|
+
|
|
67
|
+
## Explicit legacy verification
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
jep verify old-event.json \
|
|
71
|
+
--legacy \
|
|
72
|
+
--legacy-format json-sorted-v1
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Legacy selection must come from known artifact context. Do not use a failed 0.7
|
|
76
|
+
validation as evidence that an event should be treated as 0.6.
|
|
77
|
+
|
|
78
|
+
## JEP Core 0.7 model
|
|
79
|
+
|
|
80
|
+
- Event Identity is `(who,id)`.
|
|
81
|
+
- Event Hash identifies an exact signed artifact.
|
|
82
|
+
- Core does not require a top-level nonce.
|
|
83
|
+
- D requires `delegatee + scope`.
|
|
84
|
+
- T requires target `ref + termination_scope`.
|
|
85
|
+
- V requires target `ref + verification_scope + result`.
|
|
86
|
+
- chain and policy semantics remain outside Core.
|
|
87
|
+
|
|
88
|
+
## Related repositories
|
|
89
|
+
|
|
90
|
+
- JEP Core: https://github.com/hjs-spec/jep-core
|
|
91
|
+
- JEP API: https://github.com/hjs-spec/jep-api
|
|
92
|
+
- Python SDK: https://github.com/hjs-spec/sdk-py
|
|
93
|
+
- JavaScript SDK: https://github.com/hjs-spec/sdk-js
|
|
94
|
+
- Go SDK: https://github.com/hjs-spec/sdk-go
|
|
95
|
+
|
|
96
|
+
## Public draft
|
|
97
|
+
|
|
98
|
+
- https://datatracker.ietf.org/doc/draft-wang-jep-judgment-event-protocol/
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Small HTTP client for JEP
|
|
1
|
+
"""Small HTTP client for the JEP Core 0.7 CLI."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -6,6 +6,8 @@ import json
|
|
|
6
6
|
from typing import Any, Dict, Optional
|
|
7
7
|
from urllib import request, error
|
|
8
8
|
|
|
9
|
+
from . import strict_json
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
class JEPCLIError(Exception):
|
|
11
13
|
pass
|
|
@@ -26,10 +28,16 @@ class JEPClient:
|
|
|
26
28
|
self.timeout = timeout
|
|
27
29
|
|
|
28
30
|
def create_event(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
29
|
-
return self._json("POST", "/events/create", payload)
|
|
31
|
+
return self._json("POST", "/v0.7/events/create", payload)
|
|
30
32
|
|
|
31
33
|
def verify_event(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
32
|
-
return self._json("POST", "/events/verify", payload)
|
|
34
|
+
return self._json("POST", "/v0.7/events/verify", payload)
|
|
35
|
+
|
|
36
|
+
def create_event_legacy(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
37
|
+
return self._json("POST", "/events/create", payload)
|
|
38
|
+
|
|
39
|
+
def verify_event_legacy(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
40
|
+
return self._json("POST", "/events/verify-legacy", payload)
|
|
33
41
|
|
|
34
42
|
def health(self) -> Dict[str, Any]:
|
|
35
43
|
return self._json("GET", "/health", None)
|
|
@@ -38,24 +46,24 @@ class JEPClient:
|
|
|
38
46
|
data = None
|
|
39
47
|
headers = {
|
|
40
48
|
"content-type": "application/json",
|
|
41
|
-
"user-agent": "JEP-CLI/0.
|
|
49
|
+
"user-agent": "JEP-CLI/0.7.2",
|
|
42
50
|
}
|
|
43
51
|
if self.api_key:
|
|
44
52
|
headers["authorization"] = f"Bearer {self.api_key}"
|
|
45
53
|
headers["x-api-key"] = self.api_key
|
|
46
54
|
if payload is not None:
|
|
47
|
-
data =
|
|
55
|
+
data = strict_json.dumps(payload).encode("utf-8")
|
|
48
56
|
|
|
49
57
|
req = request.Request(self.base_url + path, data=data, headers=headers, method=method)
|
|
50
58
|
try:
|
|
51
59
|
with request.urlopen(req, timeout=self.timeout) as resp:
|
|
52
60
|
body = resp.read().decode("utf-8")
|
|
53
|
-
return
|
|
61
|
+
return strict_json.loads(body) if body else {}
|
|
54
62
|
except error.HTTPError as exc:
|
|
55
63
|
body = exc.read().decode("utf-8")
|
|
56
64
|
try:
|
|
57
65
|
parsed = json.loads(body)
|
|
58
66
|
except Exception:
|
|
59
67
|
parsed = {"message": body}
|
|
60
|
-
msg = parsed.get("message") or parsed.get("error") or body
|
|
68
|
+
msg = parsed.get("message") or parsed.get("detail") or parsed.get("error") or body
|
|
61
69
|
raise JEPAPIError(exc.code, msg, parsed) from exc
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""JEP
|
|
1
|
+
"""JEP Core 0.7 command line interface with explicit legacy compatibility."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -10,6 +10,7 @@ from pathlib import Path
|
|
|
10
10
|
from typing import Any, Dict
|
|
11
11
|
|
|
12
12
|
from .client import JEPClient, JEPCLIError
|
|
13
|
+
from . import strict_json
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
VALID_VERBS = {"J", "D", "T", "V"}
|
|
@@ -17,16 +18,26 @@ VALID_VERBS = {"J", "D", "T", "V"}
|
|
|
17
18
|
|
|
18
19
|
def parse_json_value(value: str) -> Any:
|
|
19
20
|
if value == "-":
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if path.exists() and path.is_file():
|
|
24
|
-
return json.loads(path.read_text(encoding="utf-8"))
|
|
21
|
+
# Read bytes when available: a text stream may replace malformed UTF-8.
|
|
22
|
+
stream = getattr(sys.stdin, "buffer", sys.stdin)
|
|
23
|
+
return strict_json.loads(stream.read())
|
|
25
24
|
|
|
26
25
|
try:
|
|
27
|
-
return
|
|
26
|
+
return strict_json.loads(value)
|
|
28
27
|
except json.JSONDecodeError:
|
|
29
|
-
|
|
28
|
+
# JSON-looking input must not become a plain string after a parse error.
|
|
29
|
+
if value.lstrip().startswith(("{", "[", '"')):
|
|
30
|
+
raise
|
|
31
|
+
# Inline event JSON may be longer than the filesystem filename limit.
|
|
32
|
+
path = Path(value)
|
|
33
|
+
try:
|
|
34
|
+
is_file = path.is_file()
|
|
35
|
+
except OSError:
|
|
36
|
+
is_file = False
|
|
37
|
+
if is_file:
|
|
38
|
+
return strict_json.loads(path.read_bytes())
|
|
39
|
+
strict_json.validate(value)
|
|
40
|
+
return value
|
|
30
41
|
|
|
31
42
|
|
|
32
43
|
def parse_ext_items(items: list[str]) -> Dict[str, Any]:
|
|
@@ -35,12 +46,15 @@ def parse_ext_items(items: list[str]) -> Dict[str, Any]:
|
|
|
35
46
|
if "=" not in item:
|
|
36
47
|
raise ValueError(f"extension must be key=json_or_string, got: {item}")
|
|
37
48
|
key, raw = item.split("=", 1)
|
|
49
|
+
if key in ext:
|
|
50
|
+
raise ValueError(f"duplicate extension: {key!r}")
|
|
51
|
+
strict_json.validate(key)
|
|
38
52
|
ext[key] = parse_json_value(raw)
|
|
39
53
|
return ext
|
|
40
54
|
|
|
41
55
|
|
|
42
56
|
def print_json(data: Any) -> None:
|
|
43
|
-
print(
|
|
57
|
+
print(strict_json.dumps(data))
|
|
44
58
|
|
|
45
59
|
|
|
46
60
|
def build_client(args: argparse.Namespace) -> JEPClient:
|
|
@@ -72,7 +86,8 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
72
86
|
if args.ext_crit:
|
|
73
87
|
payload["ext_crit"] = args.ext_crit
|
|
74
88
|
|
|
75
|
-
|
|
89
|
+
client = build_client(args)
|
|
90
|
+
result = client.create_event_legacy(payload) if args.legacy else client.create_event(payload)
|
|
76
91
|
print_json(result)
|
|
77
92
|
return 0
|
|
78
93
|
|
|
@@ -81,18 +96,21 @@ def cmd_verify(args: argparse.Namespace) -> int:
|
|
|
81
96
|
event = parse_json_value(args.event)
|
|
82
97
|
if not isinstance(event, dict):
|
|
83
98
|
raise ValueError("event must be JSON object or path to JSON event file")
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
99
|
+
client = build_client(args)
|
|
100
|
+
if args.legacy:
|
|
101
|
+
payload = {"event": event, "format": args.legacy_format}
|
|
102
|
+
result = client.verify_event_legacy(payload)
|
|
103
|
+
print_json(result)
|
|
104
|
+
return 0 if result.get("valid") is True else 2
|
|
105
|
+
|
|
106
|
+
payload = {"event": event, "mode": args.mode}
|
|
89
107
|
if args.expected_audience:
|
|
90
108
|
payload["expected_audience"] = args.expected_audience
|
|
91
|
-
if args.
|
|
92
|
-
|
|
93
|
-
result =
|
|
109
|
+
if args.max_age_seconds is not None:
|
|
110
|
+
payload["max_age_seconds"] = args.max_age_seconds
|
|
111
|
+
result = client.verify_event(payload)
|
|
94
112
|
print_json(result)
|
|
95
|
-
return 0 if result.get("
|
|
113
|
+
return 0 if result.get("status") == "valid" else 2
|
|
96
114
|
|
|
97
115
|
|
|
98
116
|
def cmd_health(args: argparse.Namespace) -> int:
|
|
@@ -112,7 +130,7 @@ def cmd_extract_event(args: argparse.Namespace) -> int:
|
|
|
112
130
|
def build_parser() -> argparse.ArgumentParser:
|
|
113
131
|
parser = argparse.ArgumentParser(
|
|
114
132
|
prog="jep",
|
|
115
|
-
description="JEP CLI
|
|
133
|
+
description="JEP CLI for JEP Core 0.7. Legacy pre-0.7 paths require explicit flags.",
|
|
116
134
|
)
|
|
117
135
|
parser.add_argument("--base-url", default="", help="JEP API base URL. Default: JEP_API_URL or http://127.0.0.1:8000")
|
|
118
136
|
parser.add_argument("--api-key", default="", help="API key. Default: JEP_API_KEY")
|
|
@@ -125,18 +143,21 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
125
143
|
create.add_argument("--who", default="", help="Actor identifier")
|
|
126
144
|
create.add_argument("--what", required=True, help="JSON value, string, file path, or '-' for stdin")
|
|
127
145
|
create.add_argument("--aud", default="", help="Audience / validation context")
|
|
128
|
-
create.add_argument("--ref", default="", help="Reference
|
|
146
|
+
create.add_argument("--ref", default="", help="Reference JSON, string, or file path")
|
|
129
147
|
create.add_argument("--ttl-minutes", type=int, default=None, help="Optional TTL extension in minutes")
|
|
130
148
|
create.add_argument("--digest-only-who", action="store_true", help="Request digest-only privacy extension")
|
|
131
149
|
create.add_argument("--ext", action="append", default=[], help="Extension key=json_or_string. May be repeated")
|
|
132
150
|
create.add_argument("--ext-crit", action="append", default=[], help="Critical extension URI. May be repeated")
|
|
151
|
+
create.add_argument("--legacy", action="store_true", help="Use explicit pre-0.7 create endpoint")
|
|
133
152
|
create.set_defaults(func=cmd_create)
|
|
134
153
|
|
|
135
154
|
verify = sub.add_parser("verify", help="Verify a JEP event")
|
|
136
155
|
verify.add_argument("event", help="Event JSON object, event JSON file path, or '-' for stdin")
|
|
137
156
|
verify.add_argument("--mode", default="archival", choices=["archival", "acceptance"], help="Validation mode")
|
|
138
|
-
verify.add_argument("--expected-audience", default="", help="
|
|
139
|
-
verify.add_argument("--
|
|
157
|
+
verify.add_argument("--expected-audience", default="", help="Optional audience required by the selected acceptance profile")
|
|
158
|
+
verify.add_argument("--max-age-seconds", type=int, default=None, help="Optional profile freshness window")
|
|
159
|
+
verify.add_argument("--legacy", action="store_true", help="Use explicit pre-0.7 legacy verifier")
|
|
160
|
+
verify.add_argument("--legacy-format", default="json-sorted-v1", choices=["json-sorted-v1", "hf-space-v06"], help="Historical decoder format; used only with --legacy")
|
|
140
161
|
verify.set_defaults(func=cmd_verify)
|
|
141
162
|
|
|
142
163
|
health = sub.add_parser("health", help="Check JEP API health")
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Strict JSON input boundary; signature/profile validation stays with the API."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import math
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import rfc8785
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _pairs(pairs):
|
|
12
|
+
result = {}
|
|
13
|
+
for key, value in pairs:
|
|
14
|
+
if key in result:
|
|
15
|
+
raise ValueError(f"duplicate JSON member: {key!r}")
|
|
16
|
+
result[key] = value
|
|
17
|
+
return result
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _constant(token):
|
|
21
|
+
raise ValueError(f"non-JSON numeric constant: {token}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def validate(value: Any) -> None:
|
|
25
|
+
"""Reject lossy/invalid input without rewriting values or signed members.
|
|
26
|
+
|
|
27
|
+
Large integer policy matches Core 0.7.4: exact binary64 integers and their
|
|
28
|
+
JCS shortest-decimal spellings are supported. This is not signature checking.
|
|
29
|
+
"""
|
|
30
|
+
if value is None or isinstance(value, bool):
|
|
31
|
+
return
|
|
32
|
+
if isinstance(value, str):
|
|
33
|
+
value.encode("utf-8", errors="strict")
|
|
34
|
+
elif isinstance(value, int):
|
|
35
|
+
if abs(value) > 2**53 - 1:
|
|
36
|
+
try:
|
|
37
|
+
number = float(value)
|
|
38
|
+
if not math.isfinite(number):
|
|
39
|
+
raise ValueError("integer exceeds binary64 range")
|
|
40
|
+
if int(number) != value and rfc8785.dumps(number) != str(value).encode("ascii"):
|
|
41
|
+
raise ValueError("integer is neither exact binary64 nor its JCS spelling")
|
|
42
|
+
except OverflowError as exc:
|
|
43
|
+
raise ValueError("integer exceeds binary64 range") from exc
|
|
44
|
+
elif isinstance(value, float):
|
|
45
|
+
if not math.isfinite(value):
|
|
46
|
+
raise ValueError("non-finite number")
|
|
47
|
+
elif isinstance(value, list):
|
|
48
|
+
for item in value:
|
|
49
|
+
validate(item)
|
|
50
|
+
elif isinstance(value, dict):
|
|
51
|
+
for key, item in value.items():
|
|
52
|
+
if not isinstance(key, str):
|
|
53
|
+
raise ValueError("JSON member names must be strings")
|
|
54
|
+
validate(key)
|
|
55
|
+
validate(item)
|
|
56
|
+
else:
|
|
57
|
+
raise ValueError("unsupported JSON value")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def loads(text: str | bytes) -> Any:
|
|
61
|
+
if isinstance(text, bytes):
|
|
62
|
+
text = text.decode("utf-8", errors="strict")
|
|
63
|
+
# Reject raw surrogate code points even before JSON parsing.
|
|
64
|
+
text.encode("utf-8", errors="strict")
|
|
65
|
+
try:
|
|
66
|
+
value = json.loads(text, object_pairs_hook=_pairs, parse_constant=_constant)
|
|
67
|
+
validate(value)
|
|
68
|
+
return value
|
|
69
|
+
except RecursionError as exc:
|
|
70
|
+
raise ValueError("JSON nesting exceeds parser capacity") from exc
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def dumps(value: Any) -> str:
|
|
74
|
+
try:
|
|
75
|
+
validate(value)
|
|
76
|
+
return json.dumps(value, ensure_ascii=False, allow_nan=False)
|
|
77
|
+
except RecursionError as exc:
|
|
78
|
+
raise ValueError("JSON nesting exceeds parser capacity") from exc
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jep-cli
|
|
3
|
+
Version: 0.7.2
|
|
4
|
+
Summary: Command-line client for JEP Core 0.7 with explicit legacy compatibility
|
|
5
|
+
Author-email: HJS Spec <signal@humanjudgment.org>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/hjs-spec/cli
|
|
8
|
+
Project-URL: Repository, https://github.com/hjs-spec/cli
|
|
9
|
+
Project-URL: Issues, https://github.com/hjs-spec/cli/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
License-File: NOTICE.md
|
|
18
|
+
Requires-Dist: rfc8785==0.1.4
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# JEP CLI — JEP Core 0.7
|
|
24
|
+
|
|
25
|
+
Command-line client for creating and verifying current JEP Core 0.7 events
|
|
26
|
+
through the JEP API.
|
|
27
|
+
|
|
28
|
+
Default endpoints:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
POST /v0.7/events/create
|
|
32
|
+
POST /v0.7/events/verify
|
|
33
|
+
GET /health
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Historical pre-0.7 handling is explicit through `--legacy`; the CLI never
|
|
37
|
+
retries a failed 0.7 event with a legacy decoder.
|
|
38
|
+
|
|
39
|
+
## Status
|
|
40
|
+
|
|
41
|
+
Experimental reference client. It does not define Core semantics or determine
|
|
42
|
+
truth, authorization validity, legal effect, causality, or policy outcome.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install jep-cli
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export JEP_API_URL=http://127.0.0.1:8000
|
|
54
|
+
export JEP_API_KEY=
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Create
|
|
58
|
+
|
|
59
|
+
Judgment:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
jep create \
|
|
63
|
+
--verb J \
|
|
64
|
+
--who did:example:agent-789 \
|
|
65
|
+
--what '{"claim":"approve","subject":"demo"}'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Delegation:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
jep create \
|
|
72
|
+
--verb D \
|
|
73
|
+
--who did:example:principal \
|
|
74
|
+
--what '{"delegatee":"did:example:agent","scope":{"actions":["read"]}}'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Termination and Verification require `--ref`. Pass a typed reference as
|
|
78
|
+
JSON when the target is another JEP event.
|
|
79
|
+
|
|
80
|
+
## Verify
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
jep verify event.json --mode archival
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
0.7 validation returns `status` plus independent `checks`, not a cumulative
|
|
87
|
+
Validation Level.
|
|
88
|
+
|
|
89
|
+
## Explicit legacy verification
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
jep verify old-event.json \
|
|
93
|
+
--legacy \
|
|
94
|
+
--legacy-format json-sorted-v1
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Legacy selection must come from known artifact context. Do not use a failed 0.7
|
|
98
|
+
validation as evidence that an event should be treated as 0.6.
|
|
99
|
+
|
|
100
|
+
## JEP Core 0.7 model
|
|
101
|
+
|
|
102
|
+
- Event Identity is `(who,id)`.
|
|
103
|
+
- Event Hash identifies an exact signed artifact.
|
|
104
|
+
- Core does not require a top-level nonce.
|
|
105
|
+
- D requires `delegatee + scope`.
|
|
106
|
+
- T requires target `ref + termination_scope`.
|
|
107
|
+
- V requires target `ref + verification_scope + result`.
|
|
108
|
+
- chain and policy semantics remain outside Core.
|
|
109
|
+
|
|
110
|
+
## Related repositories
|
|
111
|
+
|
|
112
|
+
- JEP Core: https://github.com/hjs-spec/jep-core
|
|
113
|
+
- JEP API: https://github.com/hjs-spec/jep-api
|
|
114
|
+
- Python SDK: https://github.com/hjs-spec/sdk-py
|
|
115
|
+
- JavaScript SDK: https://github.com/hjs-spec/sdk-js
|
|
116
|
+
- Go SDK: https://github.com/hjs-spec/sdk-go
|
|
117
|
+
|
|
118
|
+
## Public draft
|
|
119
|
+
|
|
120
|
+
- https://datatracker.ietf.org/doc/draft-wang-jep-judgment-event-protocol/
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
|
@@ -5,6 +5,7 @@ pyproject.toml
|
|
|
5
5
|
jep_cli/__init__.py
|
|
6
6
|
jep_cli/client.py
|
|
7
7
|
jep_cli/main.py
|
|
8
|
+
jep_cli/strict_json.py
|
|
8
9
|
jep_cli.egg-info/PKG-INFO
|
|
9
10
|
jep_cli.egg-info/SOURCES.txt
|
|
10
11
|
jep_cli.egg-info/dependency_links.txt
|
|
@@ -12,4 +13,5 @@ jep_cli.egg-info/entry_points.txt
|
|
|
12
13
|
jep_cli.egg-info/requires.txt
|
|
13
14
|
jep_cli.egg-info/top_level.txt
|
|
14
15
|
tests/test_acceptance.py
|
|
15
|
-
tests/test_cli.py
|
|
16
|
+
tests/test_cli.py
|
|
17
|
+
tests/test_strict_json.py
|
|
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "jep-cli"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Command-line
|
|
7
|
+
version = "0.7.2"
|
|
8
|
+
description = "Command-line client for JEP Core 0.7 with explicit legacy compatibility"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "HJS Spec", email = "signal@humanjudgment.org" }
|
|
13
13
|
]
|
|
14
14
|
requires-python = ">=3.9"
|
|
15
|
-
dependencies = []
|
|
15
|
+
dependencies = ["rfc8785==0.1.4"]
|
|
16
16
|
classifiers = [
|
|
17
17
|
"Development Status :: 3 - Alpha",
|
|
18
18
|
"Intended Audience :: Developers",
|