cortex-runtime 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.
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/PKG-INFO +1 -1
- cortex_runtime-0.4.0/cortex/schemas/v1/configuration.schema.json +203 -0
- cortex_runtime-0.4.0/cortex/tools/kernel/replica/__init__.py +56 -0
- cortex_runtime-0.4.0/cortex/tools/kernel/replica/identity.py +70 -0
- cortex_runtime-0.4.0/cortex/tools/kernel/replica/lease.py +227 -0
- cortex_runtime-0.4.0/cortex/tools/kernel/replica/ledger.py +292 -0
- cortex_runtime-0.4.0/cortex/tools/kernel/replica/lifecycle.py +75 -0
- cortex_runtime-0.4.0/cortex/tools/kernel/replica/router.py +324 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/pyproject.toml +1 -1
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/.gitignore +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/LICENSE +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/NOTICE +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/README.md +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/__main__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/_research/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/_research/architecture_gate.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/_research/crash_semantics.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/_research/recovery.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/_research/timeout_cancellation.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/_telemetry/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/_telemetry/benchmark.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/_telemetry/collector.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/_telemetry/models.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/cbe/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/cbe/decoder.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/cbe/encoder.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/cbe/errors.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/cbe/normalization.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/cbe/streaming.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/cbe/types.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/cbe/vectors.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/client.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/compat.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/exceptions.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/plugin.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/py.typed +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/schema/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/schema/events.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/cli/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/cli/main.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/cli/runner.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/cli/scaffolder.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/gen_test_bin.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/actors/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/actors/executor.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/actors/planner.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/context.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/drivers/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/drivers/mock_robot.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/drivers/rtl_verilator.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/graph/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/graph/analyzer.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/graph/execution_graph.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/mailbox.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/plugin/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/plugin/loader.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/plugin/manifest.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/registry.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/schema/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/schema/contract.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/schema/event.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/schema/message.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/schema/workflow.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/services/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/services/event_store.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/services/execution_intelligence.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/services/graph_builder.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/services/replay.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/services/verification.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/kernel/transport.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/run_phase2_verification.sh +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/adapters/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/adapters/base.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/adapters/coq.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/adapters/rtl.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/adapters/rust.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/archive.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/bus.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/contract.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/engine.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/generator/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/generator/composer.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/generator/program.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/generator/state.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/invariants/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/invariants/capability.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/metrics/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/metrics/base.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/metrics/opcode.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/metrics/state_space.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/metrics/trap.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/mutation.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/oracle.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/schema/__init__.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/schema/event.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verification/shrink.py +0 -0
- {cortex_runtime-0.3.0 → cortex_runtime-0.4.0}/cortex/tools/verify.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cortex-runtime
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Spatiotemporal authority and semantic verification framework for autonomous workflows
|
|
5
5
|
Project-URL: Homepage, https://github.com/Iradukunda-Fils/Cortex
|
|
6
6
|
Project-URL: Documentation, https://github.com/Iradukunda-Fils/Cortex#readme
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://cortex.security/schemas/v1/configuration.schema.json",
|
|
4
|
+
"title": "CortexDesiredConfiguration",
|
|
5
|
+
"description": "Normative canonical schema for Cortex Control Plane desired execution state. Excludes derived identity metadata (config_hash, config_generation).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"schema_version",
|
|
10
|
+
"gateway",
|
|
11
|
+
"replica_group",
|
|
12
|
+
"sandbox",
|
|
13
|
+
"resource_limits"
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"schema_version": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"enum": ["1.0.0"]
|
|
19
|
+
},
|
|
20
|
+
"gateway": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"required": [
|
|
24
|
+
"max_queue_depth",
|
|
25
|
+
"max_worker_inflight",
|
|
26
|
+
"queue_timeout_sec",
|
|
27
|
+
"dispatch_deadline_sec",
|
|
28
|
+
"selection_policy",
|
|
29
|
+
"journal_path",
|
|
30
|
+
"fsync_policy"
|
|
31
|
+
],
|
|
32
|
+
"properties": {
|
|
33
|
+
"max_queue_depth": {
|
|
34
|
+
"type": "integer",
|
|
35
|
+
"minimum": 1,
|
|
36
|
+
"maximum": 10000,
|
|
37
|
+
"default": 1000,
|
|
38
|
+
"description": "Maximum aggregate queued invocations across all replica groups before ERR_QUEUE_FULL"
|
|
39
|
+
},
|
|
40
|
+
"max_worker_inflight": {
|
|
41
|
+
"type": "integer",
|
|
42
|
+
"minimum": 1,
|
|
43
|
+
"maximum": 100,
|
|
44
|
+
"default": 10,
|
|
45
|
+
"description": "Maximum concurrent inflight invocations assigned per ready worker instance"
|
|
46
|
+
},
|
|
47
|
+
"queue_timeout_sec": {
|
|
48
|
+
"type": "number",
|
|
49
|
+
"minimum": 0.1,
|
|
50
|
+
"maximum": 300.0,
|
|
51
|
+
"default": 30.0,
|
|
52
|
+
"description": "Timeout in seconds before queued invocation raises ERR_QUEUE_TIMEOUT"
|
|
53
|
+
},
|
|
54
|
+
"dispatch_deadline_sec": {
|
|
55
|
+
"type": "number",
|
|
56
|
+
"minimum": 0.1,
|
|
57
|
+
"maximum": 60.0,
|
|
58
|
+
"default": 5.0,
|
|
59
|
+
"description": "Maximum seconds allowed to execute atomic dispatch pipeline"
|
|
60
|
+
},
|
|
61
|
+
"selection_policy": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"enum": ["least_inflight_deterministic", "round_robin_deterministic"],
|
|
64
|
+
"default": "least_inflight_deterministic",
|
|
65
|
+
"description": "Algorithm used by RoutingPolicy for worker selection"
|
|
66
|
+
},
|
|
67
|
+
"journal_path": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"pattern": "^/(?:[a-zA-Z0-9_-]+/)*[a-zA-Z0-9._-]+$",
|
|
70
|
+
"default": "/var/log/cortex/invocation_journal.jsonl",
|
|
71
|
+
"description": "Absolute filesystem path to append-only JSON-lines invocation state journal"
|
|
72
|
+
},
|
|
73
|
+
"fsync_policy": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"enum": ["always", "batch", "never"],
|
|
76
|
+
"default": "always",
|
|
77
|
+
"description": "Fsync discipline for persistent ledger journal writes"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"replica_group": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"additionalProperties": false,
|
|
84
|
+
"required": [
|
|
85
|
+
"group_id",
|
|
86
|
+
"min_replicas",
|
|
87
|
+
"max_replicas",
|
|
88
|
+
"drain_deadline_sec"
|
|
89
|
+
],
|
|
90
|
+
"properties": {
|
|
91
|
+
"group_id": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"pattern": "^[a-z0-9_-]+$",
|
|
94
|
+
"description": "Unique alphanumeric ReplicaGroup identifier"
|
|
95
|
+
},
|
|
96
|
+
"min_replicas": {
|
|
97
|
+
"type": "integer",
|
|
98
|
+
"minimum": 1,
|
|
99
|
+
"maximum": 1000,
|
|
100
|
+
"default": 1
|
|
101
|
+
},
|
|
102
|
+
"max_replicas": {
|
|
103
|
+
"type": "integer",
|
|
104
|
+
"minimum": 1,
|
|
105
|
+
"maximum": 1000,
|
|
106
|
+
"default": 10
|
|
107
|
+
},
|
|
108
|
+
"drain_deadline_sec": {
|
|
109
|
+
"type": "number",
|
|
110
|
+
"minimum": 1.0,
|
|
111
|
+
"maximum": 300.0,
|
|
112
|
+
"default": 30.0,
|
|
113
|
+
"description": "Seconds allowed for graceful worker drain before forced SIGKILL"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"sandbox": {
|
|
118
|
+
"type": "object",
|
|
119
|
+
"additionalProperties": false,
|
|
120
|
+
"required": [
|
|
121
|
+
"profile_name",
|
|
122
|
+
"required_capabilities",
|
|
123
|
+
"allowed_syscalls",
|
|
124
|
+
"landlock_paths",
|
|
125
|
+
"read_only_root",
|
|
126
|
+
"allowed_write_paths"
|
|
127
|
+
],
|
|
128
|
+
"properties": {
|
|
129
|
+
"profile_name": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"enum": ["Profile_A_Linux_Strict"],
|
|
132
|
+
"description": "Isolation profile name. Permissive/Development profiles MUST NOT be used in production schemas."
|
|
133
|
+
},
|
|
134
|
+
"required_capabilities": {
|
|
135
|
+
"type": "array",
|
|
136
|
+
"items": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"pattern": "^[a-z0-9_-]+\\.[a-z0-9._-]+$"
|
|
139
|
+
},
|
|
140
|
+
"uniqueItems": true,
|
|
141
|
+
"description": "Explicit capability identifiers (format: namespace.action)"
|
|
142
|
+
},
|
|
143
|
+
"allowed_syscalls": {
|
|
144
|
+
"type": "array",
|
|
145
|
+
"items": {
|
|
146
|
+
"type": "string",
|
|
147
|
+
"pattern": "^[a-zA-Z0-9_]+$"
|
|
148
|
+
},
|
|
149
|
+
"uniqueItems": true,
|
|
150
|
+
"description": "Whitelist of allowed Linux system call names for Seccomp filter"
|
|
151
|
+
},
|
|
152
|
+
"landlock_paths": {
|
|
153
|
+
"type": "array",
|
|
154
|
+
"items": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"pattern": "^/(?:[a-zA-Z0-9_-]+/)*[a-zA-Z0-9._-]*$"
|
|
157
|
+
},
|
|
158
|
+
"uniqueItems": true,
|
|
159
|
+
"description": "Filesystem path boundaries restricted by Landlock ABI"
|
|
160
|
+
},
|
|
161
|
+
"read_only_root": {
|
|
162
|
+
"type": "boolean",
|
|
163
|
+
"default": true,
|
|
164
|
+
"description": "Enforce read-only root filesystem mounting"
|
|
165
|
+
},
|
|
166
|
+
"allowed_write_paths": {
|
|
167
|
+
"type": "array",
|
|
168
|
+
"items": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"pattern": "^/tmp/sandbox_[a-zA-Z0-9_-]+$"
|
|
171
|
+
},
|
|
172
|
+
"default": ["/tmp/sandbox_default"],
|
|
173
|
+
"uniqueItems": true,
|
|
174
|
+
"description": "Strictly restricted write path boundaries inside worker mount namespace"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
"resource_limits": {
|
|
179
|
+
"type": "object",
|
|
180
|
+
"additionalProperties": false,
|
|
181
|
+
"required": [
|
|
182
|
+
"memory_limit_mb",
|
|
183
|
+
"cpu_quota_percent"
|
|
184
|
+
],
|
|
185
|
+
"properties": {
|
|
186
|
+
"memory_limit_mb": {
|
|
187
|
+
"type": "integer",
|
|
188
|
+
"minimum": 64,
|
|
189
|
+
"maximum": 32768,
|
|
190
|
+
"default": 512,
|
|
191
|
+
"description": "Cgroups v2 hard memory ceiling in megabytes per worker"
|
|
192
|
+
},
|
|
193
|
+
"cpu_quota_percent": {
|
|
194
|
+
"type": "integer",
|
|
195
|
+
"minimum": 10,
|
|
196
|
+
"maximum": 1600,
|
|
197
|
+
"default": 100,
|
|
198
|
+
"description": "CPU quota allocation percentage per worker"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cortex Multi-Replica Kernel Subsystem (Phases 1–4)
|
|
3
|
+
|
|
4
|
+
Provides modular components for execution identity coordinates, Gateway lease fencing,
|
|
5
|
+
durable invocation state ledgers, worker lifecycle state tracking, and routing & dispatch.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from cortex.tools.kernel.replica.identity import (
|
|
9
|
+
ExecutionIdentity,
|
|
10
|
+
OwnershipIdentity,
|
|
11
|
+
StaleConfigGenerationError,
|
|
12
|
+
)
|
|
13
|
+
from cortex.tools.kernel.replica.lease import LeaseManager, StaleLeaseError
|
|
14
|
+
from cortex.tools.kernel.replica.ledger import (
|
|
15
|
+
TERMINAL_STATES,
|
|
16
|
+
InvocationState,
|
|
17
|
+
InvocationStateLedger,
|
|
18
|
+
RecoveryBucket,
|
|
19
|
+
)
|
|
20
|
+
from cortex.tools.kernel.replica.lifecycle import WorkerLifecycleStage, WorkerLifecycleTracker
|
|
21
|
+
from cortex.tools.kernel.replica.router import (
|
|
22
|
+
CandidateResolver,
|
|
23
|
+
ExecutionClass,
|
|
24
|
+
GatewayDispatcher,
|
|
25
|
+
NoEligibleWorkerNow,
|
|
26
|
+
QueueFullError,
|
|
27
|
+
QueueTimeoutError,
|
|
28
|
+
RoutingDecisionEvent,
|
|
29
|
+
RoutingPolicy,
|
|
30
|
+
StateDomainKey,
|
|
31
|
+
WorkerRef,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"ExecutionIdentity",
|
|
36
|
+
"OwnershipIdentity",
|
|
37
|
+
"StaleConfigGenerationError",
|
|
38
|
+
"InvocationState",
|
|
39
|
+
"RecoveryBucket",
|
|
40
|
+
"InvocationStateLedger",
|
|
41
|
+
"TERMINAL_STATES",
|
|
42
|
+
"LeaseManager",
|
|
43
|
+
"StaleLeaseError",
|
|
44
|
+
"WorkerLifecycleStage",
|
|
45
|
+
"WorkerLifecycleTracker",
|
|
46
|
+
"WorkerRef",
|
|
47
|
+
"ExecutionClass",
|
|
48
|
+
"StateDomainKey",
|
|
49
|
+
"RoutingDecisionEvent",
|
|
50
|
+
"CandidateResolver",
|
|
51
|
+
"RoutingPolicy",
|
|
52
|
+
"GatewayDispatcher",
|
|
53
|
+
"NoEligibleWorkerNow",
|
|
54
|
+
"QueueFullError",
|
|
55
|
+
"QueueTimeoutError",
|
|
56
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cortex Replica Coordinate System (Phase 1)
|
|
3
|
+
|
|
4
|
+
Explicitly separates runtime worker execution coordinates (ExecutionIdentity)
|
|
5
|
+
from Gateway lease ownership coordinates (OwnershipIdentity).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StaleConfigGenerationError(Exception):
|
|
12
|
+
"""Raised when a worker operates under a stale ConfigGeneration or mismatched ConfigHash."""
|
|
13
|
+
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class ExecutionIdentity:
|
|
19
|
+
"""Uniquely identifies a specific worker execution attempt runtime coordinate.
|
|
20
|
+
|
|
21
|
+
Every worker carries both a ReplicaGeneration (deployment wave) and a
|
|
22
|
+
ConfigGeneration (immutable configuration snapshot version + SHA-256 hash).
|
|
23
|
+
The Gateway rejects workers whose config_generation or config_hash does not
|
|
24
|
+
match the active deployment.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
group_id: str
|
|
28
|
+
instance_id: str
|
|
29
|
+
generation: int
|
|
30
|
+
config_generation: int
|
|
31
|
+
attempt_id: int
|
|
32
|
+
config_hash: str = ""
|
|
33
|
+
|
|
34
|
+
def __post_init__(self) -> None:
|
|
35
|
+
if not isinstance(self.group_id, str) or not self.group_id.strip():
|
|
36
|
+
raise ValueError("group_id must be a non-empty string")
|
|
37
|
+
if not isinstance(self.instance_id, str) or not self.instance_id.strip():
|
|
38
|
+
raise ValueError("instance_id must be a non-empty string")
|
|
39
|
+
if not isinstance(self.generation, int) or self.generation < 1:
|
|
40
|
+
raise ValueError("generation must be a positive integer >= 1")
|
|
41
|
+
if not isinstance(self.config_generation, int) or self.config_generation < 1:
|
|
42
|
+
raise ValueError("config_generation must be a positive integer >= 1")
|
|
43
|
+
if not isinstance(self.attempt_id, int) or self.attempt_id < 1:
|
|
44
|
+
raise ValueError("attempt_id must be a positive integer >= 1")
|
|
45
|
+
|
|
46
|
+
def coordinate_string(self) -> str:
|
|
47
|
+
"""Returns string representation for audit logs."""
|
|
48
|
+
hash_suffix = f":h{self.config_hash[:8]}" if self.config_hash else ""
|
|
49
|
+
return f"{self.group_id}:{self.instance_id}:g{self.generation}:cfg{self.config_generation}{hash_suffix}:a{self.attempt_id}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class OwnershipIdentity:
|
|
54
|
+
"""Uniquely identifies Gateway authority and lease ownership for an invocation."""
|
|
55
|
+
|
|
56
|
+
invocation_id: str
|
|
57
|
+
lease_id: str
|
|
58
|
+
lease_epoch: int
|
|
59
|
+
|
|
60
|
+
def __post_init__(self) -> None:
|
|
61
|
+
if not isinstance(self.invocation_id, str) or not self.invocation_id.strip():
|
|
62
|
+
raise ValueError("invocation_id must be a non-empty string")
|
|
63
|
+
if not isinstance(self.lease_id, str) or not self.lease_id.strip():
|
|
64
|
+
raise ValueError("lease_id must be a non-empty string")
|
|
65
|
+
if not isinstance(self.lease_epoch, int) or self.lease_epoch < 1:
|
|
66
|
+
raise ValueError("lease_epoch must be a positive integer >= 1")
|
|
67
|
+
|
|
68
|
+
def coordinate_string(self) -> str:
|
|
69
|
+
"""Returns string representation for audit logs."""
|
|
70
|
+
return f"inv:{self.invocation_id}:lease:{self.lease_id}:ep{self.lease_epoch}"
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Linearizable Gateway Lease Manager (Phase 2 & 4)
|
|
3
|
+
|
|
4
|
+
Enforces atomic, mutually exclusive commit and revocation fencing bound to monotonic
|
|
5
|
+
LeaseEpoch counters. Stale commits are rejected with StaleLeaseError.
|
|
6
|
+
|
|
7
|
+
LINEARIZABILITY DOMAIN: Single Gateway Authority Domain.
|
|
8
|
+
Future multi-process or multi-node Gateway implementations MUST preserve
|
|
9
|
+
linearizable mutually exclusive commit and revocation fencing at this boundary.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import threading
|
|
13
|
+
import uuid
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import TYPE_CHECKING, Dict, Optional
|
|
16
|
+
|
|
17
|
+
from cortex.tools.kernel.replica.identity import OwnershipIdentity
|
|
18
|
+
from cortex.tools.kernel.replica.lifecycle import WorkerLifecycleStage
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from cortex.tools.kernel.replica.router import WorkerRef
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class StaleLeaseError(Exception):
|
|
25
|
+
"""Raised when a commit attempt uses a revoked or stale lease epoch (ERR_STALE_LEASE_EPOCH)."""
|
|
26
|
+
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class LeaseRecord:
|
|
32
|
+
ownership_identity: OwnershipIdentity
|
|
33
|
+
assigned_worker_id: str
|
|
34
|
+
active: bool = True
|
|
35
|
+
committed: bool = False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class LeaseManager:
|
|
39
|
+
"""Thread-safe, linearizable Gateway Lease Authority (Single Gateway Authority Domain)."""
|
|
40
|
+
|
|
41
|
+
def __init__(self) -> None:
|
|
42
|
+
self._lock = threading.Lock()
|
|
43
|
+
# invocation_id -> LeaseRecord
|
|
44
|
+
self._active_leases: Dict[str, LeaseRecord] = {}
|
|
45
|
+
# invocation_id -> current_epoch
|
|
46
|
+
self._latest_epoch: Dict[str, int] = {}
|
|
47
|
+
# worker_id -> WorkerRef (active registered Gateway worker state)
|
|
48
|
+
self._worker_registry: Dict[str, "WorkerRef"] = {}
|
|
49
|
+
|
|
50
|
+
def register_worker_state(self, worker_ref: "WorkerRef") -> None:
|
|
51
|
+
"""Register or update active worker replica state in the Gateway TCB registry."""
|
|
52
|
+
with self._lock:
|
|
53
|
+
self._worker_registry[worker_ref.instance_id] = worker_ref
|
|
54
|
+
|
|
55
|
+
def unregister_worker(self, worker_id: str) -> None:
|
|
56
|
+
"""Remove worker replica from Gateway TCB registry on eviction or process death."""
|
|
57
|
+
with self._lock:
|
|
58
|
+
self._worker_registry.pop(worker_id, None)
|
|
59
|
+
|
|
60
|
+
def revalidate_candidate(
|
|
61
|
+
self,
|
|
62
|
+
worker_ref: "WorkerRef",
|
|
63
|
+
active_config_gen: int,
|
|
64
|
+
active_config_hash: str,
|
|
65
|
+
active_sandbox_hash: str,
|
|
66
|
+
active_cap_hash: str,
|
|
67
|
+
max_inflight: int = 10,
|
|
68
|
+
) -> bool:
|
|
69
|
+
"""Atomically revalidate a proposed candidate snapshot inside the Gateway TCB lock."""
|
|
70
|
+
with self._lock:
|
|
71
|
+
return self._revalidate_unlocked(
|
|
72
|
+
worker_ref=worker_ref,
|
|
73
|
+
active_config_gen=active_config_gen,
|
|
74
|
+
active_config_hash=active_config_hash,
|
|
75
|
+
active_sandbox_hash=active_sandbox_hash,
|
|
76
|
+
active_cap_hash=active_cap_hash,
|
|
77
|
+
max_inflight=max_inflight,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def _revalidate_unlocked(
|
|
81
|
+
self,
|
|
82
|
+
worker_ref: "WorkerRef",
|
|
83
|
+
active_config_gen: int,
|
|
84
|
+
active_config_hash: str,
|
|
85
|
+
active_sandbox_hash: str,
|
|
86
|
+
active_cap_hash: str,
|
|
87
|
+
max_inflight: int,
|
|
88
|
+
) -> bool:
|
|
89
|
+
registered = self._worker_registry.get(worker_ref.instance_id)
|
|
90
|
+
if not registered:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
if registered.lifecycle_version != worker_ref.lifecycle_version:
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
if registered.stage != WorkerLifecycleStage.READY:
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
if registered.config_generation != active_config_gen:
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
if registered.config_hash != active_config_hash:
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
if registered.sandbox_profile_hash != active_sandbox_hash:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
if registered.capability_envelope_hash != active_cap_hash:
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
active_count = sum(
|
|
112
|
+
1 for rec in self._active_leases.values()
|
|
113
|
+
if rec.assigned_worker_id == worker_ref.instance_id and rec.active and not rec.committed
|
|
114
|
+
)
|
|
115
|
+
if active_count >= max_inflight:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
def grant_lease_with_revalidation(
|
|
121
|
+
self,
|
|
122
|
+
invocation_id: str,
|
|
123
|
+
worker_ref: "WorkerRef",
|
|
124
|
+
active_config_gen: int,
|
|
125
|
+
active_config_hash: str,
|
|
126
|
+
active_sandbox_hash: str,
|
|
127
|
+
active_cap_hash: str,
|
|
128
|
+
max_inflight: int = 10,
|
|
129
|
+
) -> Optional[OwnershipIdentity]:
|
|
130
|
+
"""Atomically revalidates candidate AND grants lease inside a single lock acquisition (Linearization Point)."""
|
|
131
|
+
with self._lock:
|
|
132
|
+
if not self._revalidate_unlocked(
|
|
133
|
+
worker_ref=worker_ref,
|
|
134
|
+
active_config_gen=active_config_gen,
|
|
135
|
+
active_config_hash=active_config_hash,
|
|
136
|
+
active_sandbox_hash=active_sandbox_hash,
|
|
137
|
+
active_cap_hash=active_cap_hash,
|
|
138
|
+
max_inflight=max_inflight,
|
|
139
|
+
):
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
current_epoch = self._latest_epoch.get(invocation_id, 0) + 1
|
|
143
|
+
self._latest_epoch[invocation_id] = current_epoch
|
|
144
|
+
|
|
145
|
+
lease_id = str(uuid.uuid4())
|
|
146
|
+
ownership = OwnershipIdentity(
|
|
147
|
+
invocation_id=invocation_id,
|
|
148
|
+
lease_id=lease_id,
|
|
149
|
+
lease_epoch=current_epoch,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
record = LeaseRecord(
|
|
153
|
+
ownership_identity=ownership,
|
|
154
|
+
assigned_worker_id=worker_ref.instance_id,
|
|
155
|
+
active=True,
|
|
156
|
+
committed=False,
|
|
157
|
+
)
|
|
158
|
+
self._active_leases[invocation_id] = record
|
|
159
|
+
return ownership
|
|
160
|
+
|
|
161
|
+
def grant_lease(self, invocation_id: str, worker_id: str) -> OwnershipIdentity:
|
|
162
|
+
"""Atomically grants an epoch-bound lease to a worker for an invocation."""
|
|
163
|
+
with self._lock:
|
|
164
|
+
current_epoch = self._latest_epoch.get(invocation_id, 0) + 1
|
|
165
|
+
self._latest_epoch[invocation_id] = current_epoch
|
|
166
|
+
|
|
167
|
+
lease_id = str(uuid.uuid4())
|
|
168
|
+
ownership = OwnershipIdentity(
|
|
169
|
+
invocation_id=invocation_id,
|
|
170
|
+
lease_id=lease_id,
|
|
171
|
+
lease_epoch=current_epoch,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
record = LeaseRecord(
|
|
175
|
+
ownership_identity=ownership,
|
|
176
|
+
assigned_worker_id=worker_id,
|
|
177
|
+
active=True,
|
|
178
|
+
committed=False,
|
|
179
|
+
)
|
|
180
|
+
self._active_leases[invocation_id] = record
|
|
181
|
+
return ownership
|
|
182
|
+
|
|
183
|
+
def commit_invocation(self, invocation_id: str, lease_epoch: int) -> bool:
|
|
184
|
+
"""Atomically commits an invocation under a specific lease epoch.
|
|
185
|
+
|
|
186
|
+
Must be mutually exclusive with revoke_lease. If lease_epoch != active lease epoch,
|
|
187
|
+
or lease is inactive, raises StaleLeaseError.
|
|
188
|
+
"""
|
|
189
|
+
with self._lock:
|
|
190
|
+
record = self._active_leases.get(invocation_id)
|
|
191
|
+
if not record:
|
|
192
|
+
raise StaleLeaseError(f"ERR_STALE_LEASE_EPOCH: No active lease found for {invocation_id}")
|
|
193
|
+
|
|
194
|
+
if not record.active:
|
|
195
|
+
raise StaleLeaseError(f"ERR_STALE_LEASE_EPOCH: Lease for {invocation_id} has been revoked")
|
|
196
|
+
|
|
197
|
+
if record.ownership_identity.lease_epoch != lease_epoch:
|
|
198
|
+
raise StaleLeaseError(
|
|
199
|
+
f"ERR_STALE_LEASE_EPOCH: Commit epoch {lease_epoch} does not match active epoch {record.ownership_identity.lease_epoch}"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
record.committed = True
|
|
203
|
+
record.active = False
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
def revoke_lease(self, invocation_id: str, lease_epoch: int) -> bool:
|
|
207
|
+
"""Atomically revokes a lease for an invocation under a specific lease epoch.
|
|
208
|
+
|
|
209
|
+
Must be mutually exclusive with commit_invocation. Returns True if lease was revoked.
|
|
210
|
+
"""
|
|
211
|
+
with self._lock:
|
|
212
|
+
record = self._active_leases.get(invocation_id)
|
|
213
|
+
if not record:
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
if record.ownership_identity.lease_epoch == lease_epoch and record.active:
|
|
217
|
+
record.active = False
|
|
218
|
+
return True
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
def is_lease_valid(self, invocation_id: str, lease_epoch: int) -> bool:
|
|
222
|
+
"""Returns True iff the lease is active and matches lease_epoch."""
|
|
223
|
+
with self._lock:
|
|
224
|
+
record = self._active_leases.get(invocation_id)
|
|
225
|
+
if not record:
|
|
226
|
+
return False
|
|
227
|
+
return record.active and record.ownership_identity.lease_epoch == lease_epoch
|