cortex-runtime 0.2.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.
- cortex/__init__.py +51 -0
- cortex/__main__.py +13 -0
- cortex/client.py +263 -0
- cortex/compat.py +20 -0
- cortex/exceptions.py +57 -0
- cortex/plugin.py +50 -0
- cortex/py.typed +1 -0
- cortex/schema/__init__.py +29 -0
- cortex/schema/events.py +127 -0
- cortex/tools/__init__.py +3 -0
- cortex/tools/cli/__init__.py +3 -0
- cortex/tools/cli/main.py +139 -0
- cortex/tools/cli/runner.py +94 -0
- cortex/tools/cli/scaffolder.py +109 -0
- cortex/tools/gen_test_bin.py +35 -0
- cortex/tools/kernel/__init__.py +3 -0
- cortex/tools/kernel/actors/__init__.py +3 -0
- cortex/tools/kernel/actors/executor.py +44 -0
- cortex/tools/kernel/actors/planner.py +38 -0
- cortex/tools/kernel/context.py +22 -0
- cortex/tools/kernel/drivers/__init__.py +3 -0
- cortex/tools/kernel/drivers/mock_robot.py +84 -0
- cortex/tools/kernel/drivers/rtl_verilator.py +49 -0
- cortex/tools/kernel/graph/__init__.py +3 -0
- cortex/tools/kernel/graph/analyzer.py +52 -0
- cortex/tools/kernel/graph/execution_graph.py +44 -0
- cortex/tools/kernel/mailbox.py +35 -0
- cortex/tools/kernel/plugin/__init__.py +3 -0
- cortex/tools/kernel/plugin/loader.py +87 -0
- cortex/tools/kernel/plugin/manifest.py +53 -0
- cortex/tools/kernel/registry.py +36 -0
- cortex/tools/kernel/schema/__init__.py +3 -0
- cortex/tools/kernel/schema/contract.py +16 -0
- cortex/tools/kernel/schema/event.py +54 -0
- cortex/tools/kernel/schema/message.py +105 -0
- cortex/tools/kernel/schema/workflow.py +38 -0
- cortex/tools/kernel/services/__init__.py +3 -0
- cortex/tools/kernel/services/event_store.py +25 -0
- cortex/tools/kernel/services/execution_intelligence.py +49 -0
- cortex/tools/kernel/services/graph_builder.py +81 -0
- cortex/tools/kernel/services/replay.py +50 -0
- cortex/tools/kernel/services/verification.py +85 -0
- cortex/tools/kernel/transport.py +50 -0
- cortex/tools/run_phase2_verification.sh +20 -0
- cortex/tools/verification/__init__.py +5 -0
- cortex/tools/verification/adapters/__init__.py +3 -0
- cortex/tools/verification/adapters/base.py +14 -0
- cortex/tools/verification/adapters/coq.py +73 -0
- cortex/tools/verification/adapters/rtl.py +77 -0
- cortex/tools/verification/adapters/rust.py +72 -0
- cortex/tools/verification/archive.py +58 -0
- cortex/tools/verification/bus.py +28 -0
- cortex/tools/verification/contract.py +70 -0
- cortex/tools/verification/engine.py +121 -0
- cortex/tools/verification/generator/__init__.py +3 -0
- cortex/tools/verification/generator/composer.py +36 -0
- cortex/tools/verification/generator/program.py +46 -0
- cortex/tools/verification/generator/state.py +54 -0
- cortex/tools/verification/invariants/__init__.py +3 -0
- cortex/tools/verification/invariants/capability.py +62 -0
- cortex/tools/verification/metrics/__init__.py +3 -0
- cortex/tools/verification/metrics/base.py +18 -0
- cortex/tools/verification/metrics/opcode.py +25 -0
- cortex/tools/verification/metrics/state_space.py +26 -0
- cortex/tools/verification/metrics/trap.py +26 -0
- cortex/tools/verification/mutation.py +48 -0
- cortex/tools/verification/oracle.py +164 -0
- cortex/tools/verification/schema/__init__.py +44 -0
- cortex/tools/verification/schema/event.py +44 -0
- cortex/tools/verification/shrink.py +26 -0
- cortex/tools/verify.py +64 -0
- cortex_runtime-0.2.0.dist-info/METADATA +220 -0
- cortex_runtime-0.2.0.dist-info/RECORD +76 -0
- cortex_runtime-0.2.0.dist-info/WHEEL +4 -0
- cortex_runtime-0.2.0.dist-info/entry_points.txt +2 -0
- cortex_runtime-0.2.0.dist-info/licenses/LICENSE +201 -0
cortex/__init__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cortex Platform Public API Package
|
|
3
|
+
|
|
4
|
+
Provides vendor-neutral, technology-neutral semantic execution layer,
|
|
5
|
+
workflow boundary management, sandboxed capability negotiation, and verification.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from cortex.client import CortexClient
|
|
9
|
+
from cortex.exceptions import (
|
|
10
|
+
CapabilityViolationError,
|
|
11
|
+
CortexError,
|
|
12
|
+
ManifestError,
|
|
13
|
+
WorkflowExecutionError,
|
|
14
|
+
)
|
|
15
|
+
from cortex.plugin import BasePlugin, Capability, PluginContext, PluginManifest
|
|
16
|
+
from cortex.schema import (
|
|
17
|
+
BaseEvent,
|
|
18
|
+
CommandIssuedEvent,
|
|
19
|
+
DriverTelemetryEvent,
|
|
20
|
+
IntentEvent,
|
|
21
|
+
PlanGeneratedEvent,
|
|
22
|
+
TelemetryEvent,
|
|
23
|
+
VerificationResultEvent,
|
|
24
|
+
Workflow,
|
|
25
|
+
WorkflowPolicy,
|
|
26
|
+
WorkflowState,
|
|
27
|
+
)
|
|
28
|
+
from cortex.tools.kernel.services.event_store import EventStoreService as EventStore
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"BaseEvent",
|
|
32
|
+
"BasePlugin",
|
|
33
|
+
"Capability",
|
|
34
|
+
"CapabilityViolationError",
|
|
35
|
+
"CommandIssuedEvent",
|
|
36
|
+
"CortexClient",
|
|
37
|
+
"CortexError",
|
|
38
|
+
"DriverTelemetryEvent",
|
|
39
|
+
"EventStore",
|
|
40
|
+
"IntentEvent",
|
|
41
|
+
"ManifestError",
|
|
42
|
+
"PlanGeneratedEvent",
|
|
43
|
+
"PluginContext",
|
|
44
|
+
"PluginManifest",
|
|
45
|
+
"TelemetryEvent",
|
|
46
|
+
"VerificationResultEvent",
|
|
47
|
+
"Workflow",
|
|
48
|
+
"WorkflowExecutionError",
|
|
49
|
+
"WorkflowPolicy",
|
|
50
|
+
"WorkflowState",
|
|
51
|
+
]
|
cortex/__main__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cortex Platform CLI Execution Entrypoint
|
|
3
|
+
|
|
4
|
+
Enables executing the Cortex Developer CLI directly via Python module dispatch:
|
|
5
|
+
python3 -m cortex <command> [options]
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from cortex.tools.cli.main import main
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
sys.exit(main())
|
cortex/client.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public CortexClient API
|
|
3
|
+
|
|
4
|
+
Main developer-facing entrypoint for orchestrating workflows, registering plugins,
|
|
5
|
+
enforcing capability sandboxes, inspecting traces, and executing deterministic replay.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from typing import cast
|
|
11
|
+
|
|
12
|
+
from cortex.plugin import BasePlugin, PluginContext
|
|
13
|
+
from cortex.schema.events import (
|
|
14
|
+
BaseEvent,
|
|
15
|
+
IntentEvent,
|
|
16
|
+
VerificationResultEvent,
|
|
17
|
+
Workflow,
|
|
18
|
+
WorkflowPolicy,
|
|
19
|
+
WorkflowState,
|
|
20
|
+
dict_to_event,
|
|
21
|
+
event_to_dict,
|
|
22
|
+
)
|
|
23
|
+
from cortex.tools.kernel.graph.analyzer import ExecutionGraphAnalyzer
|
|
24
|
+
from cortex.tools.kernel.plugin.loader import (
|
|
25
|
+
PluginRegistration,
|
|
26
|
+
PluginRegistry,
|
|
27
|
+
PluginState,
|
|
28
|
+
)
|
|
29
|
+
from cortex.tools.kernel.services.event_store import EventStoreService
|
|
30
|
+
from cortex.tools.kernel.services.graph_builder import ExecutionGraphBuilderService
|
|
31
|
+
from cortex.tools.kernel.services.replay import DeterministicReplayEngine
|
|
32
|
+
from cortex.tools.kernel.transport import AnyEvent, EventHandler, InMemoryTransport
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CortexClient:
|
|
36
|
+
"""Public high-level Python API for Cortex execution runtime."""
|
|
37
|
+
|
|
38
|
+
platform_capabilities: set[str]
|
|
39
|
+
registry: PluginRegistry
|
|
40
|
+
transport: InMemoryTransport
|
|
41
|
+
event_store: EventStoreService
|
|
42
|
+
graph_builder: ExecutionGraphBuilderService
|
|
43
|
+
registered_plugins: list[BasePlugin]
|
|
44
|
+
|
|
45
|
+
def __init__(self, platform_capabilities: set[str] | None = None) -> None:
|
|
46
|
+
if platform_capabilities is None:
|
|
47
|
+
# Default standard platform capabilities
|
|
48
|
+
self.platform_capabilities = {
|
|
49
|
+
"workflow.plan.create",
|
|
50
|
+
"workflow.command.issue",
|
|
51
|
+
"hardware.telemetry.read",
|
|
52
|
+
"verification.oracle.execute",
|
|
53
|
+
"verification.invariant.check",
|
|
54
|
+
"fs:read",
|
|
55
|
+
"exec:git",
|
|
56
|
+
"exec:pytest",
|
|
57
|
+
}
|
|
58
|
+
else:
|
|
59
|
+
self.platform_capabilities = set(platform_capabilities)
|
|
60
|
+
|
|
61
|
+
self.registry = PluginRegistry(self.platform_capabilities)
|
|
62
|
+
self.transport = InMemoryTransport()
|
|
63
|
+
self.event_store = EventStoreService()
|
|
64
|
+
self.graph_builder = ExecutionGraphBuilderService()
|
|
65
|
+
self.registered_plugins = []
|
|
66
|
+
|
|
67
|
+
# Wire global stores using named def handlers to satisfy lint & type checkers
|
|
68
|
+
def store_handler(e: AnyEvent) -> None:
|
|
69
|
+
self.event_store.record_event(e)
|
|
70
|
+
|
|
71
|
+
def builder_handler(e: AnyEvent) -> None:
|
|
72
|
+
self.graph_builder.record_message(e)
|
|
73
|
+
|
|
74
|
+
self.transport.subscribe(BaseEvent, store_handler)
|
|
75
|
+
self.transport.subscribe(BaseEvent, builder_handler)
|
|
76
|
+
|
|
77
|
+
def register_plugin(self, plugin: BasePlugin) -> PluginRegistration:
|
|
78
|
+
"""Register a plugin with capability negotiation."""
|
|
79
|
+
registration = self.registry.register(plugin.manifest)
|
|
80
|
+
if registration.state == PluginState.ACTIVE:
|
|
81
|
+
context = PluginContext(
|
|
82
|
+
session_id="default_session",
|
|
83
|
+
granted_capabilities=registration.granted_capabilities,
|
|
84
|
+
publish_func=self.transport.publish,
|
|
85
|
+
)
|
|
86
|
+
plugin.set_context(context)
|
|
87
|
+
|
|
88
|
+
def create_plugin_handler(p: BasePlugin) -> EventHandler:
|
|
89
|
+
def plugin_handler(e: AnyEvent) -> None:
|
|
90
|
+
self._dispatch_to_plugin(p, e)
|
|
91
|
+
return plugin_handler
|
|
92
|
+
|
|
93
|
+
for _ in plugin.manifest.consumes_events:
|
|
94
|
+
self.transport.subscribe(BaseEvent, create_plugin_handler(plugin))
|
|
95
|
+
|
|
96
|
+
self.registered_plugins.append(plugin)
|
|
97
|
+
return registration
|
|
98
|
+
|
|
99
|
+
def _dispatch_to_plugin(self, plugin: BasePlugin, event: AnyEvent) -> None:
|
|
100
|
+
"""Helper to safely dispatch event to plugin if active and consuming."""
|
|
101
|
+
if plugin.context and isinstance(event, BaseEvent) and type(event).__name__ in plugin.manifest.consumes_events:
|
|
102
|
+
plugin.on_event(event)
|
|
103
|
+
|
|
104
|
+
def create_workflow(self, name: str, goal: str, policy: WorkflowPolicy | None = None) -> Workflow:
|
|
105
|
+
"""Instantiate a new Workflow primitive."""
|
|
106
|
+
if policy is None:
|
|
107
|
+
policy = WorkflowPolicy()
|
|
108
|
+
return Workflow(name=name, goal=goal, policy=policy)
|
|
109
|
+
|
|
110
|
+
def run_workflow(self, workflow: Workflow, initial_intent: IntentEvent | None = None) -> Workflow:
|
|
111
|
+
"""Executes a workflow from PENDING to RUNNING to COMPLETED or FAILED."""
|
|
112
|
+
workflow.state = WorkflowState.RUNNING
|
|
113
|
+
|
|
114
|
+
# Verify whether any registered plugin was REJECTED during negotiation
|
|
115
|
+
rejected = self.registry.get_rejected_plugins()
|
|
116
|
+
if rejected:
|
|
117
|
+
denied_caps: list[str] = []
|
|
118
|
+
for r in rejected:
|
|
119
|
+
denied_caps.extend(r.denied_capabilities)
|
|
120
|
+
|
|
121
|
+
violation_event = VerificationResultEvent(
|
|
122
|
+
workflow_id=workflow.workflow_id,
|
|
123
|
+
passed=False,
|
|
124
|
+
rule_id="CAPABILITY_VIOLATION",
|
|
125
|
+
details={
|
|
126
|
+
"reason": f"Plugins rejected due to unauthorized capabilities: {denied_caps}",
|
|
127
|
+
"rejected_plugins": [r.manifest.name for r in rejected],
|
|
128
|
+
},
|
|
129
|
+
)
|
|
130
|
+
self.transport.publish(violation_event)
|
|
131
|
+
workflow.state = WorkflowState.FAILED
|
|
132
|
+
return workflow
|
|
133
|
+
|
|
134
|
+
if initial_intent is None:
|
|
135
|
+
initial_intent = IntentEvent(workflow_id=workflow.workflow_id, goal=workflow.goal)
|
|
136
|
+
|
|
137
|
+
self.transport.publish(initial_intent)
|
|
138
|
+
|
|
139
|
+
# Check for any failed verification events in store
|
|
140
|
+
failed_verifications = [
|
|
141
|
+
e for e in self.event_store.get_log()
|
|
142
|
+
if isinstance(e, VerificationResultEvent) and not e.passed
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
if failed_verifications:
|
|
146
|
+
workflow.state = WorkflowState.FAILED
|
|
147
|
+
else:
|
|
148
|
+
workflow.state = WorkflowState.COMPLETED
|
|
149
|
+
|
|
150
|
+
return workflow
|
|
151
|
+
|
|
152
|
+
def inspect_workflow(self, trace_or_id: str) -> dict[str, str | int | list[str] | list[dict[str, object]]]:
|
|
153
|
+
"""Inspects an execution trace and provides graph lineage and root cause analysis."""
|
|
154
|
+
events = self._resolve_events(trace_or_id)
|
|
155
|
+
|
|
156
|
+
builder = ExecutionGraphBuilderService()
|
|
157
|
+
for e in events:
|
|
158
|
+
builder.record_message(e)
|
|
159
|
+
|
|
160
|
+
graph = list(builder.graphs.values())[0] if builder.graphs else None
|
|
161
|
+
|
|
162
|
+
failed_nodes: list[dict[str, object]] = []
|
|
163
|
+
causality_tree: list[str] = []
|
|
164
|
+
wf_name = "Inspected Workflow"
|
|
165
|
+
wf_goal = "Trace Inspection"
|
|
166
|
+
|
|
167
|
+
if os.path.exists(trace_or_id):
|
|
168
|
+
with open(trace_or_id, "r", encoding="utf-8") as f:
|
|
169
|
+
raw_data = cast(object, json.load(f))
|
|
170
|
+
if isinstance(raw_data, dict):
|
|
171
|
+
data = cast(dict[str, object], raw_data)
|
|
172
|
+
wf_name = str(data.get("name", wf_name))
|
|
173
|
+
wf_goal = str(data.get("goal", wf_goal))
|
|
174
|
+
|
|
175
|
+
if graph:
|
|
176
|
+
analyzer = ExecutionGraphAnalyzer(graph)
|
|
177
|
+
for node in analyzer.find_failed_nodes():
|
|
178
|
+
failed_nodes.append({
|
|
179
|
+
"id": node.node_id,
|
|
180
|
+
"type": node.node_type,
|
|
181
|
+
"payload": node.payload,
|
|
182
|
+
"parent_id": node.parent_id,
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
for node_id, node in graph.nodes.items():
|
|
186
|
+
parent_info = f" -> parent: {node.parent_id[:8]}" if node.parent_id else " (ROOT)"
|
|
187
|
+
causality_tree.append(f"[{node.node_type}] ID: {node_id[:8]}{parent_info} | {node.payload}")
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
"name": wf_name,
|
|
191
|
+
"goal": wf_goal,
|
|
192
|
+
"total_events": len(events),
|
|
193
|
+
"node_count": len(graph.nodes) if graph else 0,
|
|
194
|
+
"failed_nodes": failed_nodes,
|
|
195
|
+
"causality_tree": causality_tree,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
def replay_workflow(self, trace_or_id: str) -> dict[str, str | int | bool]:
|
|
199
|
+
"""Replays an event journal and asserts 100% causal sequence immutability."""
|
|
200
|
+
events = self._resolve_events(trace_or_id)
|
|
201
|
+
|
|
202
|
+
replay_transport = InMemoryTransport()
|
|
203
|
+
replayed_events: list[BaseEvent] = []
|
|
204
|
+
|
|
205
|
+
def replay_handler(e: AnyEvent) -> None:
|
|
206
|
+
if isinstance(e, BaseEvent):
|
|
207
|
+
replayed_events.append(e)
|
|
208
|
+
|
|
209
|
+
replay_transport.subscribe(BaseEvent, replay_handler)
|
|
210
|
+
|
|
211
|
+
engine = DeterministicReplayEngine(replay_transport)
|
|
212
|
+
count = engine.replay_journal(events)
|
|
213
|
+
result = engine.verify_replayed_lineage(events, replayed_events)
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
"replayed_count": count,
|
|
217
|
+
"deterministic": cast(bool, result.get("match", False)),
|
|
218
|
+
"reason": cast(str, result.get("reason", "")),
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
def save_trace(self, workflow_id: str, filepath: str, name: str = "Workflow", goal: str = "Execution") -> str:
|
|
222
|
+
"""Saves current event store log to JSON file."""
|
|
223
|
+
events = self.event_store.get_log()
|
|
224
|
+
serialized: list[dict[str, object]] = []
|
|
225
|
+
for e in events:
|
|
226
|
+
if isinstance(e, BaseEvent):
|
|
227
|
+
serialized.append(event_to_dict(e))
|
|
228
|
+
|
|
229
|
+
payload: dict[str, object] = {
|
|
230
|
+
"name": name,
|
|
231
|
+
"goal": goal,
|
|
232
|
+
"workflow_id": workflow_id,
|
|
233
|
+
"event_count": len(serialized),
|
|
234
|
+
"events": serialized,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
os.makedirs(os.path.dirname(os.path.abspath(filepath)), exist_ok=True)
|
|
238
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
239
|
+
json.dump(payload, f, indent=2)
|
|
240
|
+
|
|
241
|
+
return filepath
|
|
242
|
+
|
|
243
|
+
def _resolve_events(self, trace_or_id: str) -> list[BaseEvent]:
|
|
244
|
+
"""Resolves file path or in-memory event log."""
|
|
245
|
+
if os.path.exists(trace_or_id):
|
|
246
|
+
with open(trace_or_id, "r", encoding="utf-8") as f:
|
|
247
|
+
raw_data = cast(object, json.load(f))
|
|
248
|
+
if isinstance(raw_data, dict):
|
|
249
|
+
data = cast(dict[str, object], raw_data)
|
|
250
|
+
raw_events: object = data.get("events", [])
|
|
251
|
+
if isinstance(raw_events, list):
|
|
252
|
+
events_list = cast(list[object], raw_events)
|
|
253
|
+
events_dict_list: list[dict[str, object]] = [
|
|
254
|
+
cast(dict[str, object], item) for item in events_list if isinstance(item, dict)
|
|
255
|
+
]
|
|
256
|
+
return [dict_to_event(e) for e in events_dict_list]
|
|
257
|
+
|
|
258
|
+
cortex_path = os.path.join(os.getcwd(), ".cortex", "events", f"{trace_or_id}.json")
|
|
259
|
+
if os.path.exists(cortex_path):
|
|
260
|
+
return self._resolve_events(cortex_path)
|
|
261
|
+
|
|
262
|
+
# Fallback to current in-memory log
|
|
263
|
+
return [e for e in self.event_store.get_log() if isinstance(e, BaseEvent)]
|
cortex/compat.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Zero-dependency compatibility helpers for Python 3.10+
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from typing import TypeVar
|
|
7
|
+
|
|
8
|
+
F = TypeVar("F", bound=Callable[..., object])
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from typing import override as override # type: ignore[attr-defined]
|
|
12
|
+
except ImportError:
|
|
13
|
+
try:
|
|
14
|
+
from typing_extensions import override as override # type: ignore[no-redef]
|
|
15
|
+
except ImportError:
|
|
16
|
+
def override(method: F, /) -> F: # type: ignore[no-redef]
|
|
17
|
+
return method
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["override"]
|
cortex/exceptions.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom Exceptions for Cortex Platform
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from cortex.compat import override
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CortexError(Exception):
|
|
10
|
+
"""Base exception class for all Cortex framework runtime errors."""
|
|
11
|
+
|
|
12
|
+
message: str
|
|
13
|
+
exit_code: int
|
|
14
|
+
|
|
15
|
+
def __init__(self, message: str, exit_code: int = 1):
|
|
16
|
+
super().__init__(message)
|
|
17
|
+
self.message = message
|
|
18
|
+
self.exit_code = exit_code
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
return self.message
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class WorkflowExecutionError(CortexError):
|
|
26
|
+
"""Raised when a workflow fails during execution or policy evaluation."""
|
|
27
|
+
|
|
28
|
+
workflow_id: str | None
|
|
29
|
+
|
|
30
|
+
def __init__(self, message: str, workflow_id: str | None = None):
|
|
31
|
+
super().__init__(message, exit_code=1)
|
|
32
|
+
self.workflow_id = workflow_id
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CapabilityViolationError(CortexError):
|
|
36
|
+
"""Raised when a plugin attempts an unauthorized action exceeding its granted capabilities."""
|
|
37
|
+
|
|
38
|
+
capability: str | None
|
|
39
|
+
|
|
40
|
+
def __init__(self, message: str, capability: str | None = None):
|
|
41
|
+
super().__init__(message, exit_code=2)
|
|
42
|
+
self.capability = capability
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ManifestError(CortexError):
|
|
46
|
+
"""Raised when a plugin manifest schema or definition is invalid."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, message: str):
|
|
49
|
+
super().__init__(message, exit_code=3)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"CapabilityViolationError",
|
|
54
|
+
"CortexError",
|
|
55
|
+
"ManifestError",
|
|
56
|
+
"WorkflowExecutionError",
|
|
57
|
+
]
|
cortex/plugin.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public Plugin Interface & Capability Management for Cortex Platform
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from cortex.schema.events import BaseEvent
|
|
10
|
+
from cortex.tools.kernel.plugin.manifest import PluginManifest
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class Capability:
|
|
15
|
+
"""Public capability representation for permission grants."""
|
|
16
|
+
name: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class PluginContext:
|
|
21
|
+
"""Runtime context provided to plugins, scoped strictly to granted capabilities."""
|
|
22
|
+
session_id: str
|
|
23
|
+
granted_capabilities: set[str]
|
|
24
|
+
publish_func: Callable[[BaseEvent], None]
|
|
25
|
+
|
|
26
|
+
def publish(self, event: BaseEvent) -> None:
|
|
27
|
+
"""Publish an event to the runtime event bus."""
|
|
28
|
+
self.publish_func(event)
|
|
29
|
+
|
|
30
|
+
def has_capability(self, cap_name: str) -> bool:
|
|
31
|
+
"""Check if a capability was granted to this plugin instance."""
|
|
32
|
+
return cap_name in self.granted_capabilities
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BasePlugin(ABC):
|
|
36
|
+
"""Abstract Base Class for all external Cortex plugins."""
|
|
37
|
+
manifest: PluginManifest
|
|
38
|
+
context: PluginContext | None
|
|
39
|
+
|
|
40
|
+
def __init__(self, manifest: PluginManifest):
|
|
41
|
+
self.manifest = manifest
|
|
42
|
+
self.context = None
|
|
43
|
+
|
|
44
|
+
def set_context(self, context: PluginContext) -> None:
|
|
45
|
+
"""Attach runtime context after capability negotiation."""
|
|
46
|
+
self.context = context
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def on_event(self, event: BaseEvent) -> None:
|
|
50
|
+
"""Handle incoming events matching the plugin's consumed event contracts."""
|
cortex/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Marker file for PEP 561.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public Cortex Schemas & Events Package
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from cortex.schema.events import (
|
|
6
|
+
BaseEvent,
|
|
7
|
+
CommandIssuedEvent,
|
|
8
|
+
DriverTelemetryEvent,
|
|
9
|
+
IntentEvent,
|
|
10
|
+
PlanGeneratedEvent,
|
|
11
|
+
TelemetryEvent,
|
|
12
|
+
VerificationResultEvent,
|
|
13
|
+
Workflow,
|
|
14
|
+
WorkflowPolicy,
|
|
15
|
+
WorkflowState,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"BaseEvent",
|
|
20
|
+
"CommandIssuedEvent",
|
|
21
|
+
"DriverTelemetryEvent",
|
|
22
|
+
"IntentEvent",
|
|
23
|
+
"PlanGeneratedEvent",
|
|
24
|
+
"TelemetryEvent",
|
|
25
|
+
"VerificationResultEvent",
|
|
26
|
+
"Workflow",
|
|
27
|
+
"WorkflowPolicy",
|
|
28
|
+
"WorkflowState",
|
|
29
|
+
]
|
cortex/schema/events.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public Event and Schema Exports for Cortex Platform
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import asdict
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
8
|
+
from cortex.tools.kernel.schema.message import (
|
|
9
|
+
BaseEvent,
|
|
10
|
+
CommandIssuedEvent,
|
|
11
|
+
DriverTelemetryEvent,
|
|
12
|
+
IntentEvent,
|
|
13
|
+
PlanGeneratedEvent,
|
|
14
|
+
VerificationResultEvent,
|
|
15
|
+
)
|
|
16
|
+
from cortex.tools.kernel.schema.workflow import Workflow, WorkflowPolicy, WorkflowState
|
|
17
|
+
|
|
18
|
+
# Public Alias
|
|
19
|
+
TelemetryEvent = DriverTelemetryEvent
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def event_to_dict(event: BaseEvent) -> dict[str, object]:
|
|
23
|
+
"""Serialize BaseEvent subclass to typed JSON-compatible dictionary."""
|
|
24
|
+
d = cast(dict[str, object], asdict(event))
|
|
25
|
+
d["_event_type"] = type(event).__name__
|
|
26
|
+
return d
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def dict_to_event(d: dict[str, object]) -> BaseEvent:
|
|
30
|
+
"""Deserialize JSON dictionary to corresponding BaseEvent subclass."""
|
|
31
|
+
d_copy = dict(d)
|
|
32
|
+
event_type = str(d_copy.pop("_event_type", "BaseEvent"))
|
|
33
|
+
|
|
34
|
+
match event_type:
|
|
35
|
+
case "IntentEvent":
|
|
36
|
+
return IntentEvent(
|
|
37
|
+
workflow_id=cast(str | None, d_copy.get("workflow_id")),
|
|
38
|
+
goal=str(d_copy.get("goal", "")),
|
|
39
|
+
parameters=cast(dict[str, object], d_copy.get("parameters", {})),
|
|
40
|
+
intent_id=str(d_copy.get("intent_id", "")),
|
|
41
|
+
event_id=str(d_copy.get("event_id", "")),
|
|
42
|
+
causation_id=cast(str | None, d_copy.get("causation_id")),
|
|
43
|
+
correlation_id=str(d_copy.get("correlation_id", "")),
|
|
44
|
+
root_id=str(d_copy.get("root_id", "")),
|
|
45
|
+
timestamp_ns=int(str(d_copy.get("timestamp_ns", 0))),
|
|
46
|
+
metadata=cast(dict[str, object], d_copy.get("metadata", {})),
|
|
47
|
+
)
|
|
48
|
+
case "PlanGeneratedEvent":
|
|
49
|
+
return PlanGeneratedEvent(
|
|
50
|
+
workflow_id=cast(str | None, d_copy.get("workflow_id")),
|
|
51
|
+
intent_id=str(d_copy.get("intent_id", "")),
|
|
52
|
+
steps=cast(list[dict[str, object]], d_copy.get("steps", [])),
|
|
53
|
+
plan_id=str(d_copy.get("plan_id", "")),
|
|
54
|
+
event_id=str(d_copy.get("event_id", "")),
|
|
55
|
+
causation_id=cast(str | None, d_copy.get("causation_id")),
|
|
56
|
+
correlation_id=str(d_copy.get("correlation_id", "")),
|
|
57
|
+
root_id=str(d_copy.get("root_id", "")),
|
|
58
|
+
timestamp_ns=int(str(d_copy.get("timestamp_ns", 0))),
|
|
59
|
+
metadata=cast(dict[str, object], d_copy.get("metadata", {})),
|
|
60
|
+
)
|
|
61
|
+
case "CommandIssuedEvent":
|
|
62
|
+
return CommandIssuedEvent(
|
|
63
|
+
workflow_id=cast(str | None, d_copy.get("workflow_id")),
|
|
64
|
+
plan_id=str(d_copy.get("plan_id", "")),
|
|
65
|
+
action=str(d_copy.get("action", "")),
|
|
66
|
+
parameters=cast(dict[str, object], d_copy.get("parameters", {})),
|
|
67
|
+
command_id=str(d_copy.get("command_id", "")),
|
|
68
|
+
event_id=str(d_copy.get("event_id", "")),
|
|
69
|
+
causation_id=cast(str | None, d_copy.get("causation_id")),
|
|
70
|
+
correlation_id=str(d_copy.get("correlation_id", "")),
|
|
71
|
+
root_id=str(d_copy.get("root_id", "")),
|
|
72
|
+
timestamp_ns=int(str(d_copy.get("timestamp_ns", 0))),
|
|
73
|
+
metadata=cast(dict[str, object], d_copy.get("metadata", {})),
|
|
74
|
+
)
|
|
75
|
+
case "DriverTelemetryEvent" | "TelemetryEvent":
|
|
76
|
+
return DriverTelemetryEvent(
|
|
77
|
+
workflow_id=cast(str | None, d_copy.get("workflow_id")),
|
|
78
|
+
driver_id=str(d_copy.get("driver_id", "")),
|
|
79
|
+
status=str(d_copy.get("status", "")),
|
|
80
|
+
payload=cast(dict[str, object], d_copy.get("payload", {})),
|
|
81
|
+
event_id=str(d_copy.get("event_id", "")),
|
|
82
|
+
causation_id=cast(str | None, d_copy.get("causation_id")),
|
|
83
|
+
correlation_id=str(d_copy.get("correlation_id", "")),
|
|
84
|
+
root_id=str(d_copy.get("root_id", "")),
|
|
85
|
+
timestamp_ns=int(str(d_copy.get("timestamp_ns", 0))),
|
|
86
|
+
metadata=cast(dict[str, object], d_copy.get("metadata", {})),
|
|
87
|
+
)
|
|
88
|
+
case "VerificationResultEvent":
|
|
89
|
+
return VerificationResultEvent(
|
|
90
|
+
workflow_id=cast(str | None, d_copy.get("workflow_id")),
|
|
91
|
+
passed=bool(d_copy.get("passed", True)),
|
|
92
|
+
rule_id=str(d_copy.get("rule_id", "")),
|
|
93
|
+
details=cast(dict[str, object], d_copy.get("details", {})),
|
|
94
|
+
metrics=cast(dict[str, object], d_copy.get("metrics", {})),
|
|
95
|
+
event_id=str(d_copy.get("event_id", "")),
|
|
96
|
+
causation_id=cast(str | None, d_copy.get("causation_id")),
|
|
97
|
+
correlation_id=str(d_copy.get("correlation_id", "")),
|
|
98
|
+
root_id=str(d_copy.get("root_id", "")),
|
|
99
|
+
timestamp_ns=int(str(d_copy.get("timestamp_ns", 0))),
|
|
100
|
+
metadata=cast(dict[str, object], d_copy.get("metadata", {})),
|
|
101
|
+
)
|
|
102
|
+
case _:
|
|
103
|
+
return BaseEvent(
|
|
104
|
+
workflow_id=cast(str | None, d_copy.get("workflow_id")),
|
|
105
|
+
event_id=str(d_copy.get("event_id", "")),
|
|
106
|
+
causation_id=cast(str | None, d_copy.get("causation_id")),
|
|
107
|
+
correlation_id=str(d_copy.get("correlation_id", "")),
|
|
108
|
+
root_id=str(d_copy.get("root_id", "")),
|
|
109
|
+
timestamp_ns=int(str(d_copy.get("timestamp_ns", 0))),
|
|
110
|
+
metadata=cast(dict[str, object], d_copy.get("metadata", {})),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
__all__ = [
|
|
115
|
+
"BaseEvent",
|
|
116
|
+
"CommandIssuedEvent",
|
|
117
|
+
"DriverTelemetryEvent",
|
|
118
|
+
"IntentEvent",
|
|
119
|
+
"PlanGeneratedEvent",
|
|
120
|
+
"TelemetryEvent",
|
|
121
|
+
"VerificationResultEvent",
|
|
122
|
+
"Workflow",
|
|
123
|
+
"WorkflowPolicy",
|
|
124
|
+
"WorkflowState",
|
|
125
|
+
"dict_to_event",
|
|
126
|
+
"event_to_dict",
|
|
127
|
+
]
|
cortex/tools/__init__.py
ADDED