aevum-cli 0.3.0__tar.gz → 0.4.0__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.
@@ -0,0 +1,49 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.pyd
6
+ .venv/
7
+ *.egg-info/
8
+
9
+ # Build
10
+ dist/
11
+ build/
12
+ site/
13
+
14
+ # Tools
15
+ .mypy_cache/
16
+ .ruff_cache/
17
+ .pytest_cache/
18
+ .hypothesis/
19
+ .cache/
20
+
21
+ # IDE
22
+ .vscode/
23
+ .idea/
24
+ *.swp
25
+ *.swo
26
+
27
+ # OS
28
+ .DS_Store
29
+ Thumbs.db
30
+
31
+ # Verify scripts (run locally, never commit)
32
+ verify_*.py
33
+ scripts/verify_*.py
34
+
35
+ # Aevum development — never commit (Phase 0+)
36
+ aevum_principles.key
37
+ signed_principles_draft.yaml
38
+ tools/sign_principles.py
39
+
40
+ # Private keys — never commit
41
+ *.key
42
+ *.pem
43
+
44
+ # OpenSSF Scorecard output (Phase 0+)
45
+ results.sarif
46
+ verify_phase3.py
47
+ verify_phase7.py
48
+ verify_phase8.py
49
+ verify_phase*.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aevum-cli
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Aevum -- command-line interface for operating Aevum nodes.
5
5
  Project-URL: Homepage, https://aevum.build
6
6
  Project-URL: Repository, https://github.com/aevum-labs/aevum
@@ -15,6 +15,8 @@ Requires-Dist: aevum-core
15
15
  Requires-Dist: aevum-server
16
16
  Requires-Dist: typer[all]>=0.12
17
17
  Requires-Dist: uvicorn[standard]>=0.30
18
+ Provides-Extra: conform
19
+ Requires-Dist: aevum-conformance; extra == 'conform'
18
20
  Provides-Extra: dev
19
21
  Requires-Dist: mypy>=1.10; extra == 'dev'
20
22
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
@@ -1,12 +1,12 @@
1
- # aevum-cli
2
-
3
- Command-line interface for Aevum — start the server, manage store migrations, and inspect complication state.
4
-
5
- ```bash
6
- pip install aevum-cli
7
- aevum server start --graph memory
8
- aevum store migrate postgres:<dsn>
9
- aevum version
10
- ```
11
-
12
- See the [main repository README](https://github.com/aevum-labs/aevum) for full usage.
1
+ # aevum-cli
2
+
3
+ Command-line interface for Aevum — start the server, manage store migrations, and inspect complication state.
4
+
5
+ ```bash
6
+ pip install aevum-cli
7
+ aevum server start --graph memory
8
+ aevum store migrate postgres:<dsn>
9
+ aevum version
10
+ ```
11
+
12
+ See the [main repository README](https://github.com/aevum-labs/aevum) for full usage.
@@ -1,66 +1,70 @@
1
- [project]
2
- name = "aevum-cli"
3
- version = "0.3.0"
4
- description = "Aevum -- command-line interface for operating Aevum nodes."
5
- readme = "README.md"
6
- requires-python = ">=3.11"
7
- license = { text = "Apache-2.0" }
8
- classifiers = [
9
- "Development Status :: 3 - Alpha",
10
- "Intended Audience :: Developers",
11
- "License :: OSI Approved :: Apache Software License",
12
- "Programming Language :: Python :: 3.11",
13
- "Typing :: Typed",
14
- ]
15
- dependencies = [
16
- "aevum-core",
17
- "aevum-server",
18
- "typer[all]>=0.12",
19
- "uvicorn[standard]>=0.30",
20
- ]
21
-
22
- [project.scripts]
23
- aevum = "aevum.cli.__main__:app"
24
-
25
- [project.urls]
26
- Homepage = "https://aevum.build"
27
- Repository = "https://github.com/aevum-labs/aevum"
28
-
29
- [build-system]
30
- requires = ["hatchling"]
31
- build-backend = "hatchling.build"
32
-
33
- [tool.hatch.build.targets.wheel]
34
- packages = ["src/aevum"]
35
-
36
- [tool.uv.sources]
37
- aevum-core = { workspace = true }
38
- aevum-server = { workspace = true }
39
-
40
- [tool.pytest.ini_options]
41
- testpaths = ["tests"]
42
- asyncio_mode = "auto"
43
- addopts = "--tb=short"
44
- pythonpath = ["src", "tests"]
45
-
46
- [tool.mypy]
47
- strict = true
48
- python_version = "3.11"
49
- mypy_path = "src"
50
- explicit_package_bases = true
51
- ignore_missing_imports = true
52
-
53
- [tool.ruff]
54
- line-length = 130
55
-
56
- [tool.ruff.lint]
57
- select = ["E", "F", "UP", "B", "SIM", "I", "ANN"]
58
- ignore = ["ANN401"]
59
-
60
- [project.optional-dependencies]
61
- dev = [
62
- "pytest>=8.0",
63
- "pytest-asyncio>=0.23",
64
- "mypy>=1.10",
65
- "ruff>=0.9",
66
- ]
1
+ [project]
2
+ name = "aevum-cli"
3
+ version = "0.4.0"
4
+ description = "Aevum -- command-line interface for operating Aevum nodes."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = { text = "Apache-2.0" }
8
+ classifiers = [
9
+ "Development Status :: 3 - Alpha",
10
+ "Intended Audience :: Developers",
11
+ "License :: OSI Approved :: Apache Software License",
12
+ "Programming Language :: Python :: 3.11",
13
+ "Typing :: Typed",
14
+ ]
15
+ dependencies = [
16
+ "aevum-core",
17
+ "aevum-server",
18
+ "typer[all]>=0.12",
19
+ "uvicorn[standard]>=0.30",
20
+ ]
21
+
22
+ [project.scripts]
23
+ aevum = "aevum.cli.__main__:app"
24
+
25
+ [project.urls]
26
+ Homepage = "https://aevum.build"
27
+ Repository = "https://github.com/aevum-labs/aevum"
28
+
29
+ [build-system]
30
+ requires = ["hatchling"]
31
+ build-backend = "hatchling.build"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/aevum"]
35
+
36
+ [tool.uv.sources]
37
+ aevum-core = { workspace = true }
38
+ aevum-server = { workspace = true }
39
+ aevum-conformance = { workspace = true }
40
+
41
+ [tool.pytest.ini_options]
42
+ testpaths = ["tests"]
43
+ asyncio_mode = "auto"
44
+ addopts = "--tb=short"
45
+ pythonpath = ["src", "tests"]
46
+
47
+ [tool.mypy]
48
+ strict = true
49
+ python_version = "3.11"
50
+ mypy_path = "src"
51
+ explicit_package_bases = true
52
+ ignore_missing_imports = true
53
+
54
+ [tool.ruff]
55
+ line-length = 130
56
+
57
+ [tool.ruff.lint]
58
+ select = ["E", "F", "UP", "B", "SIM", "I", "ANN"]
59
+ ignore = ["ANN401"]
60
+
61
+ [project.optional-dependencies]
62
+ conform = [
63
+ "aevum-conformance",
64
+ ]
65
+ dev = [
66
+ "pytest>=8.0",
67
+ "pytest-asyncio>=0.23",
68
+ "mypy>=1.10",
69
+ "ruff>=0.9",
70
+ ]
@@ -1,14 +1,14 @@
1
- """
2
- aevum.cli -- Command-line interface for operating Aevum nodes.
3
-
4
- Usage: aevum <command> [options]
5
-
6
- Commands:
7
- version Print installed package versions
8
- server start Start the HTTP API server
9
- store migrate Migrate between graph backends
10
- complication Manage installed complications
11
- conformance run Run the conformance suite
12
- """
13
-
14
- __version__ = "0.1.0"
1
+ """
2
+ aevum.cli -- Command-line interface for operating Aevum nodes.
3
+
4
+ Usage: aevum <command> [options]
5
+
6
+ Commands:
7
+ version Print installed package versions
8
+ server start Start the HTTP API server
9
+ store migrate Migrate between graph backends
10
+ complication Manage installed complications
11
+ conformance run Run the conformance suite
12
+ """
13
+
14
+ __version__ = "0.4.0"
@@ -1,9 +1,9 @@
1
- """
2
- Entry point: aevum <command>
3
- Invoked via [project.scripts] aevum = "aevum.cli.__main__:app"
4
- """
5
-
6
- from aevum.cli.app import app
7
-
8
- if __name__ == "__main__":
9
- app()
1
+ """
2
+ Entry point: aevum <command>
3
+ Invoked via [project.scripts] aevum = "aevum.cli.__main__:app"
4
+ """
5
+
6
+ from aevum.cli.app import app
7
+
8
+ if __name__ == "__main__":
9
+ app()
@@ -0,0 +1,252 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2024-2026 Aevum Labs contributors
3
+ """
4
+ Top-level typer app — CLI v2. Sub-commands and direct commands registered here.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ from pathlib import Path
10
+ from typing import Annotated
11
+
12
+ import typer
13
+
14
+ from aevum.cli.commands import complication, conformance, server, store, version
15
+
16
+ # Module-level import for mock.patch patchability (Rule 57).
17
+ # Soft import: aevum-conformance is a workspace package not on PyPI, so
18
+ # callers without it installed still get a usable CLI (conform command
19
+ # shows a helpful error instead of crashing at startup).
20
+ try:
21
+ from aevum.conformance.suite import ConformanceSuite
22
+ except ImportError: # pragma: no cover
23
+ ConformanceSuite = None # type: ignore[assignment,misc]
24
+
25
+ app = typer.Typer(
26
+ name="aevum",
27
+ help="Aevum governed context kernel — CLI v2",
28
+ no_args_is_help=True,
29
+ pretty_exceptions_enable=False,
30
+ rich_markup_mode="markdown",
31
+ )
32
+
33
+ app.add_typer(server.app, name="server")
34
+ app.add_typer(store.app, name="store")
35
+ app.add_typer(complication.app, name="complication")
36
+ app.add_typer(conformance.app, name="conformance")
37
+ app.command(name="version")(version.version_command)
38
+
39
+ _DEFAULT_STATE = Path.home() / ".aevum"
40
+
41
+
42
+ @app.command()
43
+ def init(
44
+ state_dir: Annotated[
45
+ Path,
46
+ typer.Option("--state-dir", "-s", help="State directory path"),
47
+ ] = _DEFAULT_STATE,
48
+ principles: Annotated[
49
+ Path,
50
+ typer.Option("--principles", "-p", help="Path to signed_principles.yaml"),
51
+ ] = Path("signed_principles.yaml"),
52
+ ) -> None:
53
+ """
54
+ Initialize Aevum state directory and verify principles.
55
+
56
+ Creates the state directory, generates dual signing keys (Ed25519 +
57
+ ML-DSA-65), and verifies the signed_principles.yaml file.
58
+ """
59
+ typer.echo(f"Initializing Aevum state at {state_dir}...")
60
+
61
+ try:
62
+ from aevum.core.principles import PrinciplesVerifier
63
+ verifier = PrinciplesVerifier(principles)
64
+ p = verifier.verify()
65
+ typer.echo(f" Principles: OK (sequence={p.sequence}, signed_by={p.signed_by[:30]}...)")
66
+ except Exception as exc: # noqa: BLE001
67
+ typer.echo(f" Principles: FAILED — {exc}", err=True)
68
+ raise typer.Exit(code=1) from None
69
+
70
+ try:
71
+ from aevum.core.kernel import Kernel
72
+ kernel = Kernel.local(
73
+ state_dir=state_dir,
74
+ principles_path=principles,
75
+ tsa_enabled=False,
76
+ )
77
+ ed25519_pub = kernel.signer.ed25519_public_key.hex()[:16]
78
+ typer.echo(f" Keys: OK (ed25519={ed25519_pub}...)")
79
+ typer.echo(f" Canaries: PASS ({len(kernel.principles.immutable_ids())} immutable principles)")
80
+ except Exception as exc: # noqa: BLE001
81
+ typer.echo(f" Kernel init: FAILED — {exc}", err=True)
82
+ raise typer.Exit(code=1) from None
83
+
84
+ typer.echo(typer.style("Aevum initialized successfully.", fg=typer.colors.GREEN))
85
+
86
+
87
+ @app.command()
88
+ def verify(
89
+ session_id: Annotated[str, typer.Argument(help="Session ID to verify")],
90
+ state_dir: Annotated[
91
+ Path,
92
+ typer.Option("--state-dir", "-s"),
93
+ ] = _DEFAULT_STATE,
94
+ ) -> None:
95
+ """
96
+ Verify a session's Merkle root and signatures.
97
+
98
+ Re-reads the stored session events from SQLite, recomputes the
99
+ Merkle root, and compares it to the signed root in the sigchain.
100
+ """
101
+ db_path = state_dir / "aevum.db"
102
+ if not db_path.exists():
103
+ typer.echo(f"Database not found: {db_path}", err=True)
104
+ raise typer.Exit(code=1)
105
+
106
+ try:
107
+ from aevum.core.replay import ReplayEngine
108
+ engine = ReplayEngine(db_path)
109
+ result = engine.replay(session_id)
110
+
111
+ if result.all_matched:
112
+ typer.echo(typer.style(
113
+ f"Session {session_id[:8]}... VERIFIED",
114
+ fg=typer.colors.GREEN,
115
+ ))
116
+ typer.echo(f" Merkle root: {result.original_merkle_root[:16]}...")
117
+ typer.echo(f" Events: {len(result.event_results)}")
118
+ else:
119
+ typer.echo(typer.style(
120
+ f"Session {session_id[:8]}... TAMPERED",
121
+ fg=typer.colors.RED,
122
+ ), err=True)
123
+ typer.echo(
124
+ f" First divergence: event #{result.first_divergence}", err=True
125
+ )
126
+ raise typer.Exit(code=1)
127
+
128
+ except ValueError as exc:
129
+ typer.echo(f"Session not found: {exc}", err=True)
130
+ raise typer.Exit(code=1) from None
131
+
132
+
133
+ @app.command(name="audit-pack")
134
+ def audit_pack(
135
+ session_id: Annotated[str, typer.Argument(help="Session ID")],
136
+ output: Annotated[
137
+ Path | None,
138
+ typer.Option("--output", "-o", help="Output file path (default: stdout)"),
139
+ ] = None,
140
+ state_dir: Annotated[
141
+ Path,
142
+ typer.Option("--state-dir", "-s"),
143
+ ] = _DEFAULT_STATE,
144
+ ) -> None:
145
+ """
146
+ Export EU AI Act Article 12 audit pack for a session.
147
+
148
+ Produces a JSON-LD document using the PROV-O vocabulary.
149
+ """
150
+ db_path = state_dir / "aevum.db"
151
+ if not db_path.exists():
152
+ typer.echo(f"Database not found: {db_path}", err=True)
153
+ raise typer.Exit(code=1)
154
+
155
+ try:
156
+ from aevum.core.audit.audit_pack import AuditPackExporter
157
+ exporter = AuditPackExporter(db_path)
158
+ json_text = exporter.export_json(session_id)
159
+
160
+ if output:
161
+ output.write_text(json_text, encoding="utf-8")
162
+ typer.echo(f"Audit pack written to {output}")
163
+ else:
164
+ typer.echo(json_text)
165
+
166
+ except Exception as exc: # noqa: BLE001
167
+ typer.echo(f"Audit pack error: {exc}", err=True)
168
+ raise typer.Exit(code=1) from None
169
+
170
+
171
+ @app.command()
172
+ def conform(
173
+ output: Annotated[
174
+ str,
175
+ typer.Option("--output", "-o", help="Output format: text or json"),
176
+ ] = "text",
177
+ ) -> None:
178
+ """
179
+ Run the 9-invariant conformance suite.
180
+
181
+ Tests all required Aevum behavioral invariants and prints a report.
182
+ Exit code 0 = all pass, 1 = one or more fail.
183
+ """
184
+ if ConformanceSuite is None:
185
+ typer.echo(
186
+ "aevum-conformance is not installed. Install it with: pip install aevum-conformance",
187
+ err=True,
188
+ )
189
+ raise typer.Exit(code=1)
190
+ suite = ConformanceSuite()
191
+ result = suite.run_all()
192
+
193
+ if output == "json":
194
+ typer.echo(json.dumps(result.to_dict(), indent=2))
195
+ else:
196
+ typer.echo(result.render())
197
+
198
+ if not result.all_passed:
199
+ raise typer.Exit(code=1)
200
+
201
+
202
+ @app.command()
203
+ def replay(
204
+ session_id: Annotated[str, typer.Argument(help="Session ID to replay")],
205
+ verbose: Annotated[
206
+ bool,
207
+ typer.Option("--verbose", "-v", help="Show per-event results"),
208
+ ] = False,
209
+ state_dir: Annotated[
210
+ Path,
211
+ typer.Option("--state-dir", "-s"),
212
+ ] = _DEFAULT_STATE,
213
+ ) -> None:
214
+ """
215
+ Replay a session and verify Merkle chain integrity.
216
+
217
+ Re-reads all events and recomputes the Merkle root. Reports any
218
+ divergence from the stored root (indicating tampering).
219
+ """
220
+ db_path = state_dir / "aevum.db"
221
+ if not db_path.exists():
222
+ typer.echo(f"Database not found: {db_path}", err=True)
223
+ raise typer.Exit(code=1)
224
+
225
+ try:
226
+ from aevum.core.replay import ReplayEngine
227
+ engine = ReplayEngine(db_path)
228
+ result = engine.replay(session_id)
229
+
230
+ status = (
231
+ typer.style("PASS", fg=typer.colors.GREEN)
232
+ if result.all_matched
233
+ else typer.style("FAIL", fg=typer.colors.RED)
234
+ )
235
+ typer.echo(f"Replay {session_id[:8]}...: {status}")
236
+ typer.echo(f" Events: {len(result.event_results)}")
237
+ typer.echo(f" Merkle root: {result.original_merkle_root[:16]}...")
238
+
239
+ if verbose:
240
+ for ev in result.event_results:
241
+ ev_status = "OK" if ev.matched else "DIVERGED"
242
+ typer.echo(f" [{ev.sequence:3d}] {ev.event_type:<12} {ev_status}")
243
+
244
+ if not result.all_matched:
245
+ typer.echo(
246
+ f" First divergence: event #{result.first_divergence}", err=True
247
+ )
248
+ raise typer.Exit(code=1)
249
+
250
+ except ValueError as exc:
251
+ typer.echo(f"Session not found: {exc}", err=True)
252
+ raise typer.Exit(code=1) from None
@@ -1 +1 @@
1
- """aevum.cli.commands -- sub-command modules."""
1
+ """aevum.cli.commands -- sub-command modules."""
@@ -1,60 +1,60 @@
1
- """
2
- aevum complication list/suspend/resume -- manage complications.
3
- """
4
-
5
- from __future__ import annotations
6
-
7
- from typing import Annotated
8
-
9
- import typer
10
-
11
- app = typer.Typer(help="Manage installed complications.")
12
-
13
-
14
- @app.command("list")
15
- def list_complications(
16
- graph: Annotated[str, typer.Option(help="Graph backend")] = "memory",
17
- ) -> None:
18
- """List all installed complications with state and health."""
19
- from aevum.core.engine import Engine
20
- engine = Engine()
21
- complications = engine.list_complications()
22
- if not complications:
23
- typer.echo("No complications installed.")
24
- return
25
- typer.echo(f"Installed complications ({len(complications)}):")
26
- for name, entry in complications.items():
27
- state = entry["state"]
28
- typer.echo(f" {name}: {state}")
29
-
30
-
31
- @app.command("suspend")
32
- def suspend(
33
- name: Annotated[str, typer.Argument(help="Complication name to suspend")],
34
- ) -> None:
35
- """Suspend an ACTIVE complication."""
36
- from aevum.core.engine import Engine
37
- from aevum.core.exceptions import ComplicationError
38
- engine = Engine()
39
- try:
40
- engine.suspend_complication(name)
41
- typer.echo(f"Suspended: {name}")
42
- except ComplicationError as e:
43
- typer.echo(f"Error: {e}", err=True)
44
- raise typer.Exit(code=1) from e
45
-
46
-
47
- @app.command("resume")
48
- def resume(
49
- name: Annotated[str, typer.Argument(help="Complication name to resume")],
50
- ) -> None:
51
- """Resume a SUSPENDED complication."""
52
- from aevum.core.engine import Engine
53
- from aevum.core.exceptions import ComplicationError
54
- engine = Engine()
55
- try:
56
- engine.resume_complication(name)
57
- typer.echo(f"Resumed: {name}")
58
- except ComplicationError as e:
59
- typer.echo(f"Error: {e}", err=True)
60
- raise typer.Exit(code=1) from e
1
+ """
2
+ aevum complication list/suspend/resume -- manage complications.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Annotated
8
+
9
+ import typer
10
+
11
+ app = typer.Typer(help="Manage installed complications.")
12
+
13
+
14
+ @app.command("list")
15
+ def list_complications(
16
+ graph: Annotated[str, typer.Option(help="Graph backend")] = "memory",
17
+ ) -> None:
18
+ """List all installed complications with state and health."""
19
+ from aevum.core.engine import Engine
20
+ engine = Engine()
21
+ complications = engine.list_complications()
22
+ if not complications:
23
+ typer.echo("No complications installed.")
24
+ return
25
+ typer.echo(f"Installed complications ({len(complications)}):")
26
+ for name, entry in complications.items():
27
+ state = entry["state"]
28
+ typer.echo(f" {name}: {state}")
29
+
30
+
31
+ @app.command("suspend")
32
+ def suspend(
33
+ name: Annotated[str, typer.Argument(help="Complication name to suspend")],
34
+ ) -> None:
35
+ """Suspend an ACTIVE complication."""
36
+ from aevum.core.engine import Engine
37
+ from aevum.core.exceptions import ComplicationError
38
+ engine = Engine()
39
+ try:
40
+ engine.suspend_complication(name)
41
+ typer.echo(f"Suspended: {name}")
42
+ except ComplicationError as e:
43
+ typer.echo(f"Error: {e}", err=True)
44
+ raise typer.Exit(code=1) from e
45
+
46
+
47
+ @app.command("resume")
48
+ def resume(
49
+ name: Annotated[str, typer.Argument(help="Complication name to resume")],
50
+ ) -> None:
51
+ """Resume a SUSPENDED complication."""
52
+ from aevum.core.engine import Engine
53
+ from aevum.core.exceptions import ComplicationError
54
+ engine = Engine()
55
+ try:
56
+ engine.resume_complication(name)
57
+ typer.echo(f"Resumed: {name}")
58
+ except ComplicationError as e:
59
+ typer.echo(f"Error: {e}", err=True)
60
+ raise typer.Exit(code=1) from e