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.
- aevum_cli-0.4.0/.gitignore +49 -0
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/PKG-INFO +3 -1
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/README.md +12 -12
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/pyproject.toml +70 -66
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/src/aevum/cli/__init__.py +14 -14
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/src/aevum/cli/__main__.py +9 -9
- aevum_cli-0.4.0/src/aevum/cli/app.py +252 -0
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/src/aevum/cli/commands/__init__.py +1 -1
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/src/aevum/cli/commands/complication.py +60 -60
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/src/aevum/cli/commands/conformance.py +46 -46
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/src/aevum/cli/commands/server.py +92 -92
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/src/aevum/cli/commands/store.py +51 -51
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/src/aevum/cli/commands/version.py +29 -32
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/tests/test_cli.py +99 -99
- aevum_cli-0.4.0/tests/test_phase8_cli.py +352 -0
- aevum_cli-0.3.0/.gitignore +0 -31
- aevum_cli-0.3.0/src/aevum/cli/app.py +0 -22
- {aevum_cli-0.3.0 → aevum_cli-0.4.0}/src/aevum/cli/py.typed +0 -0
|
@@ -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
|
+
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.
|
|
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
|
|
38
|
-
aevum-server
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
"""
|
|
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
|