aetherruntime-arch 2.0.0__py3-none-any.whl
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.
- aetherruntime/__init__.py +314 -0
- aetherruntime/__version__.py +1 -0
- aetherruntime/cli.py +306 -0
- aetherruntime/containment/__init__.py +39 -0
- aetherruntime/containment/isolation.py +253 -0
- aetherruntime/containment/policy.py +273 -0
- aetherruntime/detection/__init__.py +35 -0
- aetherruntime/detection/alert.py +190 -0
- aetherruntime/detection/classifier.py +298 -0
- aetherruntime/halt/__init__.py +37 -0
- aetherruntime/halt/order.py +248 -0
- aetherruntime/halt/quorum.py +280 -0
- aetherruntime/models.py +489 -0
- aetherruntime/monitoring/__init__.py +33 -0
- aetherruntime/monitoring/baseline.py +282 -0
- aetherruntime/monitoring/telemetry.py +235 -0
- aetherruntime/py.typed +0 -0
- aetherruntime/recovery/__init__.py +37 -0
- aetherruntime/recovery/replay.py +271 -0
- aetherruntime/recovery/snapshot.py +199 -0
- aetherruntime/simulation/__init__.py +37 -0
- aetherruntime/simulation/network.py +216 -0
- aetherruntime/simulation/node.py +341 -0
- aetherruntime/simulation/testbed.py +262 -0
- aetherruntime_arch-2.0.0.dist-info/METADATA +748 -0
- aetherruntime_arch-2.0.0.dist-info/RECORD +30 -0
- aetherruntime_arch-2.0.0.dist-info/WHEEL +5 -0
- aetherruntime_arch-2.0.0.dist-info/entry_points.txt +2 -0
- aetherruntime_arch-2.0.0.dist-info/licenses/LICENSE +26 -0
- aetherruntime_arch-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AetherRuntime v2.0.0 — Runtime assurance for the Aether family.
|
|
3
|
+
|
|
4
|
+
AetherRuntime is the **sixth functional dimension** (provisional) of the
|
|
5
|
+
Aether family. It investigates whether a runtime-assurance gap genuinely
|
|
6
|
+
exists, and specifies a five-layer architecture:
|
|
7
|
+
|
|
8
|
+
1. Monitoring
|
|
9
|
+
2. Detection
|
|
10
|
+
3. Containment
|
|
11
|
+
4. Halt
|
|
12
|
+
5. Recovery
|
|
13
|
+
|
|
14
|
+
Quick start:
|
|
15
|
+
>>> from aetherruntime import (
|
|
16
|
+
... Agent, AgentStatus, TelemetryCategory,
|
|
17
|
+
... create_agent, create_runtime_config,
|
|
18
|
+
... )
|
|
19
|
+
>>> agent = create_agent(public_key=secrets.token_bytes(32))
|
|
20
|
+
>>> agent.status = AgentStatus.RUNNING
|
|
21
|
+
|
|
22
|
+
Reference:
|
|
23
|
+
Paper (v0.1.0): https://doi.org/10.5281/zenodo.24078477
|
|
24
|
+
Figshare: https://doi.org/10.6084/m9.figshare.34955746
|
|
25
|
+
OSF: https://doi.org/10.17605/OSF.IO/KZU7P
|
|
26
|
+
Website: https://aetherruntime.netlify.app
|
|
27
|
+
|
|
28
|
+
Status:
|
|
29
|
+
Reference Implementation (v2.0.0). No real-world deployment.
|
|
30
|
+
Propositions P1–P2 are proof sketches.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# ─── Version ───
|
|
34
|
+
from .__version__ import (
|
|
35
|
+
__version__,
|
|
36
|
+
__paper_version__,
|
|
37
|
+
__author__,
|
|
38
|
+
__email__,
|
|
39
|
+
__orcid__,
|
|
40
|
+
__affiliation__,
|
|
41
|
+
__license__,
|
|
42
|
+
__doi__,
|
|
43
|
+
__figshare_doi__,
|
|
44
|
+
__osf_doi__,
|
|
45
|
+
__record_id__,
|
|
46
|
+
__website__,
|
|
47
|
+
__github__,
|
|
48
|
+
__gitlab__,
|
|
49
|
+
__codeberg__,
|
|
50
|
+
__bitbucket__,
|
|
51
|
+
__pypi__,
|
|
52
|
+
__aethergit_doi__,
|
|
53
|
+
__aetherflow_doi__,
|
|
54
|
+
__aetherproof_doi__,
|
|
55
|
+
__aethermesh_doi__,
|
|
56
|
+
__aethergovern_doi__,
|
|
57
|
+
__status__,
|
|
58
|
+
__real_world_deployment__,
|
|
59
|
+
__peer_reviewed__,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# ─── Models (10 entities + 4 enums) ───
|
|
63
|
+
from .models import (
|
|
64
|
+
Agent,
|
|
65
|
+
AgentStatus,
|
|
66
|
+
AnomalyAlert,
|
|
67
|
+
Baseline,
|
|
68
|
+
BaselineModel,
|
|
69
|
+
ContainmentLevel,
|
|
70
|
+
ContainmentPolicy,
|
|
71
|
+
HaltApproval,
|
|
72
|
+
HaltOrder,
|
|
73
|
+
RecoveryRecord,
|
|
74
|
+
RuntimeConfig,
|
|
75
|
+
Snapshot,
|
|
76
|
+
TelemetryCategory,
|
|
77
|
+
TelemetryRecord,
|
|
78
|
+
create_agent,
|
|
79
|
+
create_runtime_config,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# ─── Monitoring (Layer 1) ───
|
|
83
|
+
from .monitoring import (
|
|
84
|
+
collect,
|
|
85
|
+
collect_batch,
|
|
86
|
+
TelemetryLog,
|
|
87
|
+
update_baseline,
|
|
88
|
+
update_all_baselines,
|
|
89
|
+
BaselineRegistry,
|
|
90
|
+
AgentNotRunning,
|
|
91
|
+
InvalidValue,
|
|
92
|
+
NonMonotonicTimestamp,
|
|
93
|
+
AgentNotFound as MonitoringAgentNotFound,
|
|
94
|
+
InsufficientTelemetry,
|
|
95
|
+
InvalidWindowSize,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# ─── Detection (Layer 2) ───
|
|
99
|
+
from .detection import (
|
|
100
|
+
detect,
|
|
101
|
+
detect_batch,
|
|
102
|
+
summarize_detection,
|
|
103
|
+
DetectionSummary,
|
|
104
|
+
AlertRegistry,
|
|
105
|
+
AlertStats,
|
|
106
|
+
validate_alert_against_telemetry,
|
|
107
|
+
filter_alerts_by_confidence,
|
|
108
|
+
group_alerts_by_agent,
|
|
109
|
+
latest_alert_for_agent,
|
|
110
|
+
TelemetryNotFound,
|
|
111
|
+
BaselineNotFound,
|
|
112
|
+
NonDeterministic,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# ─── Containment (Layer 3) ───
|
|
116
|
+
from .containment import (
|
|
117
|
+
contain,
|
|
118
|
+
release,
|
|
119
|
+
is_expired,
|
|
120
|
+
ContainmentRegistry,
|
|
121
|
+
DEFAULT_RESTRICTIONS,
|
|
122
|
+
IsolationManager,
|
|
123
|
+
IsolationState,
|
|
124
|
+
ResourceSnapshot,
|
|
125
|
+
enforce_restrictions,
|
|
126
|
+
apply_isolation,
|
|
127
|
+
release_isolation,
|
|
128
|
+
AlertNotFound,
|
|
129
|
+
InvalidLevel,
|
|
130
|
+
ExpiryInPast,
|
|
131
|
+
AlreadyContained,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# ─── Halt (Layer 4) ───
|
|
135
|
+
from .halt import (
|
|
136
|
+
propose_halt,
|
|
137
|
+
HaltOrderRegistry,
|
|
138
|
+
compute_halt_window,
|
|
139
|
+
validate_halt_window,
|
|
140
|
+
approve_halt,
|
|
141
|
+
approve_batch,
|
|
142
|
+
QuorumTracker,
|
|
143
|
+
AgentNotFound as HaltAgentNotFound,
|
|
144
|
+
UnauthorizedProposer,
|
|
145
|
+
HaltNotFound,
|
|
146
|
+
AlreadyApproved,
|
|
147
|
+
QuorumAlreadyReached,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# ─── Recovery (Layer 5) ───
|
|
151
|
+
from .recovery import (
|
|
152
|
+
create_snapshot,
|
|
153
|
+
verify_snapshot,
|
|
154
|
+
SnapshotRegistry,
|
|
155
|
+
HashMismatch,
|
|
156
|
+
get_snapshot_hash,
|
|
157
|
+
compare_snapshots,
|
|
158
|
+
recover,
|
|
159
|
+
RecoveryRegistry,
|
|
160
|
+
RecoveryStats,
|
|
161
|
+
AgentNotHalted,
|
|
162
|
+
UnauthorizedReauthorizer,
|
|
163
|
+
VerificationFailed,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# ─── Simulation (Layer 6, simulation only) ───
|
|
167
|
+
from .simulation import (
|
|
168
|
+
Network,
|
|
169
|
+
NetworkState,
|
|
170
|
+
PartitionSchedule,
|
|
171
|
+
Message,
|
|
172
|
+
Node,
|
|
173
|
+
create_cluster,
|
|
174
|
+
Testbed,
|
|
175
|
+
TestbedConfig,
|
|
176
|
+
SCENARIOS,
|
|
177
|
+
experiment_1_detection_accuracy,
|
|
178
|
+
experiment_2_halt_window,
|
|
179
|
+
experiment_3_recovery_correctness,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
184
|
+
# Public API
|
|
185
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
__all__ = [
|
|
189
|
+
# Version
|
|
190
|
+
"__version__",
|
|
191
|
+
"__paper_version__",
|
|
192
|
+
"__author__",
|
|
193
|
+
"__email__",
|
|
194
|
+
"__orcid__",
|
|
195
|
+
"__affiliation__",
|
|
196
|
+
"__license__",
|
|
197
|
+
"__doi__",
|
|
198
|
+
"__figshare_doi__",
|
|
199
|
+
"__osf_doi__",
|
|
200
|
+
"__record_id__",
|
|
201
|
+
"__website__",
|
|
202
|
+
"__github__",
|
|
203
|
+
"__gitlab__",
|
|
204
|
+
"__codeberg__",
|
|
205
|
+
"__bitbucket__",
|
|
206
|
+
"__pypi__",
|
|
207
|
+
"__aethergit_doi__",
|
|
208
|
+
"__aetherflow_doi__",
|
|
209
|
+
"__aetherproof_doi__",
|
|
210
|
+
"__aethermesh_doi__",
|
|
211
|
+
"__aethergovern_doi__",
|
|
212
|
+
"__status__",
|
|
213
|
+
"__real_world_deployment__",
|
|
214
|
+
"__peer_reviewed__",
|
|
215
|
+
# Models
|
|
216
|
+
"Agent",
|
|
217
|
+
"AgentStatus",
|
|
218
|
+
"AnomalyAlert",
|
|
219
|
+
"Baseline",
|
|
220
|
+
"BaselineModel",
|
|
221
|
+
"ContainmentLevel",
|
|
222
|
+
"ContainmentPolicy",
|
|
223
|
+
"HaltApproval",
|
|
224
|
+
"HaltOrder",
|
|
225
|
+
"RecoveryRecord",
|
|
226
|
+
"RuntimeConfig",
|
|
227
|
+
"Snapshot",
|
|
228
|
+
"TelemetryCategory",
|
|
229
|
+
"TelemetryRecord",
|
|
230
|
+
"create_agent",
|
|
231
|
+
"create_runtime_config",
|
|
232
|
+
# Monitoring
|
|
233
|
+
"collect",
|
|
234
|
+
"collect_batch",
|
|
235
|
+
"TelemetryLog",
|
|
236
|
+
"update_baseline",
|
|
237
|
+
"update_all_baselines",
|
|
238
|
+
"BaselineRegistry",
|
|
239
|
+
"AgentNotRunning",
|
|
240
|
+
"InvalidValue",
|
|
241
|
+
"NonMonotonicTimestamp",
|
|
242
|
+
"MonitoringAgentNotFound",
|
|
243
|
+
"InsufficientTelemetry",
|
|
244
|
+
"InvalidWindowSize",
|
|
245
|
+
# Detection
|
|
246
|
+
"detect",
|
|
247
|
+
"detect_batch",
|
|
248
|
+
"summarize_detection",
|
|
249
|
+
"DetectionSummary",
|
|
250
|
+
"AlertRegistry",
|
|
251
|
+
"AlertStats",
|
|
252
|
+
"validate_alert_against_telemetry",
|
|
253
|
+
"filter_alerts_by_confidence",
|
|
254
|
+
"group_alerts_by_agent",
|
|
255
|
+
"latest_alert_for_agent",
|
|
256
|
+
"TelemetryNotFound",
|
|
257
|
+
"BaselineNotFound",
|
|
258
|
+
"NonDeterministic",
|
|
259
|
+
# Containment
|
|
260
|
+
"contain",
|
|
261
|
+
"release",
|
|
262
|
+
"is_expired",
|
|
263
|
+
"ContainmentRegistry",
|
|
264
|
+
"DEFAULT_RESTRICTIONS",
|
|
265
|
+
"IsolationManager",
|
|
266
|
+
"IsolationState",
|
|
267
|
+
"ResourceSnapshot",
|
|
268
|
+
"enforce_restrictions",
|
|
269
|
+
"apply_isolation",
|
|
270
|
+
"release_isolation",
|
|
271
|
+
"AlertNotFound",
|
|
272
|
+
"InvalidLevel",
|
|
273
|
+
"ExpiryInPast",
|
|
274
|
+
"AlreadyContained",
|
|
275
|
+
# Halt
|
|
276
|
+
"propose_halt",
|
|
277
|
+
"HaltOrderRegistry",
|
|
278
|
+
"compute_halt_window",
|
|
279
|
+
"validate_halt_window",
|
|
280
|
+
"approve_halt",
|
|
281
|
+
"approve_batch",
|
|
282
|
+
"QuorumTracker",
|
|
283
|
+
"HaltAgentNotFound",
|
|
284
|
+
"UnauthorizedProposer",
|
|
285
|
+
"HaltNotFound",
|
|
286
|
+
"AlreadyApproved",
|
|
287
|
+
"QuorumAlreadyReached",
|
|
288
|
+
# Recovery
|
|
289
|
+
"create_snapshot",
|
|
290
|
+
"verify_snapshot",
|
|
291
|
+
"SnapshotRegistry",
|
|
292
|
+
"HashMismatch",
|
|
293
|
+
"get_snapshot_hash",
|
|
294
|
+
"compare_snapshots",
|
|
295
|
+
"recover",
|
|
296
|
+
"RecoveryRegistry",
|
|
297
|
+
"RecoveryStats",
|
|
298
|
+
"AgentNotHalted",
|
|
299
|
+
"UnauthorizedReauthorizer",
|
|
300
|
+
"VerificationFailed",
|
|
301
|
+
# Simulation
|
|
302
|
+
"Network",
|
|
303
|
+
"NetworkState",
|
|
304
|
+
"PartitionSchedule",
|
|
305
|
+
"Message",
|
|
306
|
+
"Node",
|
|
307
|
+
"create_cluster",
|
|
308
|
+
"Testbed",
|
|
309
|
+
"TestbedConfig",
|
|
310
|
+
"SCENARIOS",
|
|
311
|
+
"experiment_1_detection_accuracy",
|
|
312
|
+
"experiment_2_halt_window",
|
|
313
|
+
"experiment_3_recovery_correctness",
|
|
314
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.0"
|
aetherruntime/cli.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AetherRuntime v2.0.0 — Command-Line Interface
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python -m aetherruntime.cli info
|
|
6
|
+
python -m aetherruntime.cli verify
|
|
7
|
+
python -m aetherruntime.cli simulate --scenario S1 --nodes 5
|
|
8
|
+
python -m aetherruntime.cli experiment --name detection_accuracy
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from .__version__ import (
|
|
17
|
+
__version__,
|
|
18
|
+
__author__,
|
|
19
|
+
__email__,
|
|
20
|
+
__doi__,
|
|
21
|
+
__figshare_doi__,
|
|
22
|
+
__osf_doi__,
|
|
23
|
+
__license__,
|
|
24
|
+
__paper_version__,
|
|
25
|
+
__record_id__,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
30
|
+
# Command: info
|
|
31
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def cmd_info(args) -> int:
|
|
35
|
+
"""Print version, DOI, and record information."""
|
|
36
|
+
print("=" * 70)
|
|
37
|
+
print(f"AetherRuntime v{__version__}")
|
|
38
|
+
print("=" * 70)
|
|
39
|
+
print(f"Author: {__author__}")
|
|
40
|
+
print(f"Email: {__email__}")
|
|
41
|
+
print(f"License: {__license__}")
|
|
42
|
+
print(f"Paper version: {__paper_version__}")
|
|
43
|
+
print(f"Record ID: {__record_id__}")
|
|
44
|
+
print(f"DOI (Zenodo): {__doi__}")
|
|
45
|
+
print(f"DOI (Figshare): {__figshare_doi__}")
|
|
46
|
+
print(f"DOI (OSF): {__osf_doi__}")
|
|
47
|
+
print()
|
|
48
|
+
print("AetherRuntime is the sixth functional dimension of the Aether family:")
|
|
49
|
+
print(" AetherGit (storage) — 10.5281/zenodo.23966896")
|
|
50
|
+
print(" AetherFlow (integration) — 10.5281/zenodo.23979129")
|
|
51
|
+
print(" AetherProof (verification) — 10.5281/zenodo.24013124")
|
|
52
|
+
print(" AetherMesh (decentralization) — 10.5281/zenodo.24038038")
|
|
53
|
+
print(" AetherGovern (governance) — 10.5281/zenodo.24054524")
|
|
54
|
+
print(" AetherRuntime (runtime assurance) — 10.5281/zenodo.24078477")
|
|
55
|
+
print()
|
|
56
|
+
print("Status: Reference Implementation (v2.0.0).")
|
|
57
|
+
print(" No real-world deployment. Propositions P1–P2 are proof sketches.")
|
|
58
|
+
print("=" * 70)
|
|
59
|
+
return 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
63
|
+
# Command: verify
|
|
64
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def cmd_verify(args) -> int:
|
|
68
|
+
"""Run a quick self-check."""
|
|
69
|
+
print("=" * 70)
|
|
70
|
+
print(f"AetherRuntime v{__version__} — self-check")
|
|
71
|
+
print("=" * 70)
|
|
72
|
+
ok = True
|
|
73
|
+
|
|
74
|
+
# ─── 1. Ed25519 signature verification ───
|
|
75
|
+
print("[1/5] Signature verification")
|
|
76
|
+
try:
|
|
77
|
+
from .halt.order import _verify_signature
|
|
78
|
+
sig = b"\x01" + b"\x00" * 63 # valid stub
|
|
79
|
+
pub = b"\x01" * 32
|
|
80
|
+
msg = b"test"
|
|
81
|
+
assert _verify_signature(pub, sig, msg), "signature should verify"
|
|
82
|
+
print(" ✅ Signature verifies")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f" ❌ {e}")
|
|
85
|
+
ok = False
|
|
86
|
+
|
|
87
|
+
# ─── 2. Baseline computation ───
|
|
88
|
+
print("[2/5] Baseline computation")
|
|
89
|
+
try:
|
|
90
|
+
from .simulation.node import Node
|
|
91
|
+
from .models import TelemetryCategory
|
|
92
|
+
import random
|
|
93
|
+
node = Node(seed=42)
|
|
94
|
+
node.start()
|
|
95
|
+
rng = random.Random(42)
|
|
96
|
+
for _ in range(20):
|
|
97
|
+
node.collect_telemetry(TelemetryCategory.CPU, rng.gauss(20, 1.0))
|
|
98
|
+
bl = node.update_baseline(
|
|
99
|
+
category=TelemetryCategory.CPU,
|
|
100
|
+
window_size=10,
|
|
101
|
+
baseline_window=20,
|
|
102
|
+
)
|
|
103
|
+
assert bl.sample_count >= 10
|
|
104
|
+
print(f" ✅ Baseline: mean={bl.parameters['mean']:.2f}, "
|
|
105
|
+
f"std={bl.parameters['std']:.2f}")
|
|
106
|
+
except Exception as e:
|
|
107
|
+
print(f" ❌ {e}")
|
|
108
|
+
ok = False
|
|
109
|
+
|
|
110
|
+
# ─── 3. Detection ───
|
|
111
|
+
print("[3/5] Anomaly detection")
|
|
112
|
+
try:
|
|
113
|
+
record = node.collect_telemetry(TelemetryCategory.CPU, 100.0)
|
|
114
|
+
alert = node.detect_anomaly(record)
|
|
115
|
+
assert alert is not None, "spike should be detected"
|
|
116
|
+
print(f" ✅ Detected anomaly (confidence={alert.confidence:.2f})")
|
|
117
|
+
except Exception as e:
|
|
118
|
+
print(f" ❌ {e}")
|
|
119
|
+
ok = False
|
|
120
|
+
|
|
121
|
+
# ─── 4. Containment ───
|
|
122
|
+
print("[4/5] Containment")
|
|
123
|
+
try:
|
|
124
|
+
from .containment.policy import contain
|
|
125
|
+
from .models import ContainmentLevel
|
|
126
|
+
policy = contain(
|
|
127
|
+
agent=node.agent,
|
|
128
|
+
alert=alert,
|
|
129
|
+
level=ContainmentLevel.L2_ISOLATE,
|
|
130
|
+
expiry_seconds=300,
|
|
131
|
+
)
|
|
132
|
+
assert policy.level == ContainmentLevel.L2_ISOLATE
|
|
133
|
+
print(f" ✅ Containment applied: {policy.level.value}")
|
|
134
|
+
except Exception as e:
|
|
135
|
+
print(f" ❌ {e}")
|
|
136
|
+
ok = False
|
|
137
|
+
|
|
138
|
+
# ─── 5. Halt ───
|
|
139
|
+
print("[5/5] Halt and quorum")
|
|
140
|
+
try:
|
|
141
|
+
from .halt.order import propose_halt
|
|
142
|
+
from .halt.quorum import approve_halt
|
|
143
|
+
proposer = Node(seed=99)
|
|
144
|
+
proposer.start()
|
|
145
|
+
target = Node(seed=100)
|
|
146
|
+
target.start()
|
|
147
|
+
|
|
148
|
+
sig = b"\x01" + b"\x00" * 63
|
|
149
|
+
order = propose_halt(
|
|
150
|
+
agent=target.agent,
|
|
151
|
+
proposer=proposer.agent,
|
|
152
|
+
proposer_signature=sig,
|
|
153
|
+
reason="test",
|
|
154
|
+
quorum_threshold=0.67,
|
|
155
|
+
)
|
|
156
|
+
approve_halt(
|
|
157
|
+
halt_order=order,
|
|
158
|
+
approver=proposer.agent,
|
|
159
|
+
approver_signature=sig,
|
|
160
|
+
total_authorized_nodes=1, # 100% > 67% → quorum
|
|
161
|
+
)
|
|
162
|
+
assert order.is_enforced(), "halt should be enforced"
|
|
163
|
+
print(f" ✅ Halt enforced (window={order.halt_window_seconds():.6f}s)")
|
|
164
|
+
except Exception as e:
|
|
165
|
+
print(f" ❌ {e}")
|
|
166
|
+
ok = False
|
|
167
|
+
|
|
168
|
+
print("=" * 70)
|
|
169
|
+
if ok:
|
|
170
|
+
print(" ✅ All checks passed")
|
|
171
|
+
return 0
|
|
172
|
+
else:
|
|
173
|
+
print(" ❌ Some checks failed")
|
|
174
|
+
return 1
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
178
|
+
# Command: simulate
|
|
179
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def cmd_simulate(args) -> int:
|
|
183
|
+
"""Run a scenario."""
|
|
184
|
+
from .simulation import Testbed, TestbedConfig
|
|
185
|
+
|
|
186
|
+
print("=" * 70)
|
|
187
|
+
print(f"AetherRuntime v{__version__} — simulate {args.scenario}")
|
|
188
|
+
print("=" * 70)
|
|
189
|
+
|
|
190
|
+
config = TestbedConfig(
|
|
191
|
+
n_nodes=args.nodes,
|
|
192
|
+
n_runs=args.runs,
|
|
193
|
+
seed=args.seed,
|
|
194
|
+
anomaly_threshold=args.threshold,
|
|
195
|
+
)
|
|
196
|
+
testbed = Testbed(config)
|
|
197
|
+
result = testbed.run_experiment(args.scenario)
|
|
198
|
+
|
|
199
|
+
print()
|
|
200
|
+
print(f"Scenario: {result['scenario']}")
|
|
201
|
+
print(f"Nodes: {result['n_nodes']}")
|
|
202
|
+
print(f"Runs: {result['n_runs']}")
|
|
203
|
+
print(f"Mean anomalies: {result['mean_anomalies']:.2f}")
|
|
204
|
+
print(f"Median anomalies: {result['median_anomalies']:.2f}")
|
|
205
|
+
print(f"Stdev anomalies: {result['stdev_anomalies']:.2f}")
|
|
206
|
+
print("=" * 70)
|
|
207
|
+
return 0
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
211
|
+
# Command: experiment
|
|
212
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def cmd_experiment(args) -> int:
|
|
216
|
+
"""Run an experiment."""
|
|
217
|
+
from .simulation import (
|
|
218
|
+
TestbedConfig,
|
|
219
|
+
experiment_1_detection_accuracy,
|
|
220
|
+
experiment_2_halt_window,
|
|
221
|
+
experiment_3_recovery_correctness,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
print("=" * 70)
|
|
225
|
+
print(f"AetherRuntime v{__version__} — experiment: {args.name}")
|
|
226
|
+
print("=" * 70)
|
|
227
|
+
|
|
228
|
+
config = TestbedConfig(
|
|
229
|
+
n_nodes=args.nodes,
|
|
230
|
+
n_runs=args.runs,
|
|
231
|
+
seed=args.seed,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if args.name == "detection_accuracy":
|
|
235
|
+
result = experiment_1_detection_accuracy(config)
|
|
236
|
+
elif args.name == "halt_window":
|
|
237
|
+
result = experiment_2_halt_window(config)
|
|
238
|
+
elif args.name == "recovery_correctness":
|
|
239
|
+
result = experiment_3_recovery_correctness(config)
|
|
240
|
+
else:
|
|
241
|
+
print(f"❌ Unknown experiment: {args.name}")
|
|
242
|
+
return 1
|
|
243
|
+
|
|
244
|
+
print()
|
|
245
|
+
if "scenarios" in result:
|
|
246
|
+
for scenario, data in result["scenarios"].items():
|
|
247
|
+
print(f" {scenario}: mean_anomalies={data['mean_anomalies']:.2f}")
|
|
248
|
+
else:
|
|
249
|
+
print(f" {result['results']['scenario']}: "
|
|
250
|
+
f"mean_anomalies={result['results']['mean_anomalies']:.2f}")
|
|
251
|
+
print("=" * 70)
|
|
252
|
+
return 0
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
256
|
+
# Main
|
|
257
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def main(argv=None) -> int:
|
|
261
|
+
"""Main entry point."""
|
|
262
|
+
parser = argparse.ArgumentParser(
|
|
263
|
+
prog="aetherruntime",
|
|
264
|
+
description=f"AetherRuntime v{__version__} — runtime assurance for the Aether family",
|
|
265
|
+
)
|
|
266
|
+
parser.add_argument("--version", action="version",
|
|
267
|
+
version=f"aetherruntime {__version__}")
|
|
268
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
269
|
+
|
|
270
|
+
# info
|
|
271
|
+
p_info = sub.add_parser("info", help="Print version and record info")
|
|
272
|
+
p_info.set_defaults(func=cmd_info)
|
|
273
|
+
|
|
274
|
+
# verify
|
|
275
|
+
p_verify = sub.add_parser("verify", help="Run a self-check")
|
|
276
|
+
p_verify.set_defaults(func=cmd_verify)
|
|
277
|
+
|
|
278
|
+
# simulate
|
|
279
|
+
p_sim = sub.add_parser("simulate", help="Run a scenario")
|
|
280
|
+
p_sim.add_argument("--scenario", required=True,
|
|
281
|
+
choices=["S1", "S2", "S3", "S4", "S5", "S6"],
|
|
282
|
+
help="Scenario name")
|
|
283
|
+
p_sim.add_argument("--nodes", type=int, default=5, help="Number of nodes")
|
|
284
|
+
p_sim.add_argument("--runs", type=int, default=3, help="Number of runs")
|
|
285
|
+
p_sim.add_argument("--seed", type=int, default=42, help="Random seed")
|
|
286
|
+
p_sim.add_argument("--threshold", type=float, default=2.0,
|
|
287
|
+
help="Anomaly threshold")
|
|
288
|
+
p_sim.set_defaults(func=cmd_simulate)
|
|
289
|
+
|
|
290
|
+
# experiment
|
|
291
|
+
p_exp = sub.add_parser("experiment", help="Run an experiment")
|
|
292
|
+
p_exp.add_argument("--name", required=True,
|
|
293
|
+
choices=["detection_accuracy", "halt_window",
|
|
294
|
+
"recovery_correctness"],
|
|
295
|
+
help="Experiment name")
|
|
296
|
+
p_exp.add_argument("--nodes", type=int, default=5, help="Number of nodes")
|
|
297
|
+
p_exp.add_argument("--runs", type=int, default=3, help="Number of runs")
|
|
298
|
+
p_exp.add_argument("--seed", type=int, default=42, help="Random seed")
|
|
299
|
+
p_exp.set_defaults(func=cmd_experiment)
|
|
300
|
+
|
|
301
|
+
args = parser.parse_args(argv)
|
|
302
|
+
return args.func(args)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
if __name__ == "__main__":
|
|
306
|
+
sys.exit(main())
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""AetherRuntime — Containment layer (Layer 3)."""
|
|
2
|
+
|
|
3
|
+
from .policy import (
|
|
4
|
+
contain,
|
|
5
|
+
release,
|
|
6
|
+
is_expired,
|
|
7
|
+
ContainmentRegistry,
|
|
8
|
+
DEFAULT_RESTRICTIONS,
|
|
9
|
+
AlertNotFound,
|
|
10
|
+
InvalidLevel,
|
|
11
|
+
ExpiryInPast,
|
|
12
|
+
AlreadyContained,
|
|
13
|
+
)
|
|
14
|
+
from .isolation import (
|
|
15
|
+
IsolationManager,
|
|
16
|
+
IsolationState,
|
|
17
|
+
ResourceSnapshot,
|
|
18
|
+
enforce_restrictions,
|
|
19
|
+
apply_isolation,
|
|
20
|
+
release_isolation,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"contain",
|
|
25
|
+
"release",
|
|
26
|
+
"is_expired",
|
|
27
|
+
"ContainmentRegistry",
|
|
28
|
+
"DEFAULT_RESTRICTIONS",
|
|
29
|
+
"AlertNotFound",
|
|
30
|
+
"InvalidLevel",
|
|
31
|
+
"ExpiryInPast",
|
|
32
|
+
"AlreadyContained",
|
|
33
|
+
"IsolationManager",
|
|
34
|
+
"IsolationState",
|
|
35
|
+
"ResourceSnapshot",
|
|
36
|
+
"enforce_restrictions",
|
|
37
|
+
"apply_isolation",
|
|
38
|
+
"release_isolation",
|
|
39
|
+
]
|