dr-exec 0.1.1__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.
- dr_exec-0.1.1/PKG-INFO +55 -0
- dr_exec-0.1.1/README.md +43 -0
- dr_exec-0.1.1/pyproject.toml +46 -0
- dr_exec-0.1.1/pyproject.toml.orig +37 -0
- dr_exec-0.1.1/src/dr_exec/__init__.py +227 -0
- dr_exec-0.1.1/src/dr_exec/_bootstrap.py +206 -0
- dr_exec-0.1.1/src/dr_exec/_declaration.py +131 -0
- dr_exec-0.1.1/src/dr_exec/_identity.py +285 -0
- dr_exec-0.1.1/src/dr_exec/_model.py +251 -0
- dr_exec-0.1.1/src/dr_exec/_probe.py +85 -0
- dr_exec-0.1.1/src/dr_exec/_protocol.py +438 -0
- dr_exec-0.1.1/src/dr_exec/_provenance.py +104 -0
- dr_exec-0.1.1/src/dr_exec/_retention.py +137 -0
- dr_exec-0.1.1/src/dr_exec/_scheduler.py +621 -0
- dr_exec-0.1.1/src/dr_exec/_spawn.py +271 -0
- dr_exec-0.1.1/src/dr_exec/_wire.py +48 -0
- dr_exec-0.1.1/src/dr_exec/cancel.py +25 -0
- dr_exec-0.1.1/src/dr_exec/declare.py +390 -0
- dr_exec-0.1.1/src/dr_exec/engine.py +1605 -0
- dr_exec-0.1.1/src/dr_exec/errors.py +13 -0
- dr_exec-0.1.1/src/dr_exec/executor.py +175 -0
- dr_exec-0.1.1/src/dr_exec/fake.py +157 -0
- dr_exec-0.1.1/src/dr_exec/kinds.py +134 -0
- dr_exec-0.1.1/src/dr_exec/names.py +16 -0
- dr_exec-0.1.1/src/dr_exec/pool.py +510 -0
- dr_exec-0.1.1/src/dr_exec/protocols.py +75 -0
- dr_exec-0.1.1/src/dr_exec/py.typed +0 -0
- dr_exec-0.1.1/src/dr_exec/record.py +463 -0
- dr_exec-0.1.1/src/dr_exec/runtime.py +141 -0
- dr_exec-0.1.1/src/dr_exec/store.py +568 -0
dr_exec-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: dr-exec
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Contract-driven local process execution
|
|
5
|
+
Author: Danielle Rothermel
|
|
6
|
+
Author-email: Danielle Rothermel <danielle.rothermel@gmail.com>
|
|
7
|
+
Requires-Dist: dr-serialize==0.1.1
|
|
8
|
+
Requires-Dist: dr-store>=0.1.1
|
|
9
|
+
Requires-Dist: pydantic==2.13.4
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# dr-exec
|
|
14
|
+
|
|
15
|
+
Contract-driven local process execution.
|
|
16
|
+
|
|
17
|
+
## Status
|
|
18
|
+
|
|
19
|
+
The complete v1 surface is implemented:
|
|
20
|
+
|
|
21
|
+
- identity and serialization — canonical secret-safe projections, the strict
|
|
22
|
+
bounded read path, and role-specific versioned identities;
|
|
23
|
+
- the isolated-host Python runtime with its fixed `-I` probe and the
|
|
24
|
+
library-owned child wrapper;
|
|
25
|
+
- the protected protocol — request transport, frame codec, the
|
|
26
|
+
prelude/output/completion state machine, and finite executor self-budgets;
|
|
27
|
+
- durable recording — `DirectoryRunStore` over the pinned Document
|
|
28
|
+
Directory, with typed lifecycle handles, receipts, and strict load
|
|
29
|
+
validation;
|
|
30
|
+
- the single-run engine behind `ProcessExecutor.run` — spawn through
|
|
31
|
+
teardown for trusted-command, untrusted-command, and untrusted-Python
|
|
32
|
+
targets, with budget enforcement, best-effort attribution, and the
|
|
33
|
+
mandatory record lifecycle;
|
|
34
|
+
- `FakeExecutor`, which enforces the same declaration rules and carries an
|
|
35
|
+
explicit fake receipt, plus the shared behavioral conformance suite both
|
|
36
|
+
executors pass;
|
|
37
|
+
- the bounded execution pool — one scheduler core behind
|
|
38
|
+
`ProcessExecutor.run_many`, `ProcessExecutor.open_pool`, and
|
|
39
|
+
`ExecutionPool.run_stream`, with one shared resident bound over running
|
|
40
|
+
and completed-but-undelivered submissions, completion-order delivery,
|
|
41
|
+
backpressure on intake, cancellation, drain, and abort.
|
|
42
|
+
|
|
43
|
+
Execution runs on macOS; the engine refuses other platforms at the
|
|
44
|
+
declaration boundary, and the tests that need real process semantics are
|
|
45
|
+
marked accordingly.
|
|
46
|
+
|
|
47
|
+
- Exact public API: [`src/dr_exec/__init__.py`](src/dr_exec/__init__.py)
|
|
48
|
+
- Stable capability boundaries:
|
|
49
|
+
[`src/dr_exec/protocols.py`](src/dr_exec/protocols.py)
|
|
50
|
+
- Planned behavioral contracts:
|
|
51
|
+
[`docs/v1-plan/contracts.toml`](docs/v1-plan/contracts.toml)
|
|
52
|
+
- Design and qualification mechanics:
|
|
53
|
+
[`docs/v1-plan/v1-design.md`](docs/v1-plan/v1-design.md)
|
|
54
|
+
- Shared serialization capabilities delivered by the dr-serialize pin:
|
|
55
|
+
[`docs/v1-plan/dr-serialize-additions.md`](docs/v1-plan/dr-serialize-additions.md)
|
dr_exec-0.1.1/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# dr-exec
|
|
2
|
+
|
|
3
|
+
Contract-driven local process execution.
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
The complete v1 surface is implemented:
|
|
8
|
+
|
|
9
|
+
- identity and serialization — canonical secret-safe projections, the strict
|
|
10
|
+
bounded read path, and role-specific versioned identities;
|
|
11
|
+
- the isolated-host Python runtime with its fixed `-I` probe and the
|
|
12
|
+
library-owned child wrapper;
|
|
13
|
+
- the protected protocol — request transport, frame codec, the
|
|
14
|
+
prelude/output/completion state machine, and finite executor self-budgets;
|
|
15
|
+
- durable recording — `DirectoryRunStore` over the pinned Document
|
|
16
|
+
Directory, with typed lifecycle handles, receipts, and strict load
|
|
17
|
+
validation;
|
|
18
|
+
- the single-run engine behind `ProcessExecutor.run` — spawn through
|
|
19
|
+
teardown for trusted-command, untrusted-command, and untrusted-Python
|
|
20
|
+
targets, with budget enforcement, best-effort attribution, and the
|
|
21
|
+
mandatory record lifecycle;
|
|
22
|
+
- `FakeExecutor`, which enforces the same declaration rules and carries an
|
|
23
|
+
explicit fake receipt, plus the shared behavioral conformance suite both
|
|
24
|
+
executors pass;
|
|
25
|
+
- the bounded execution pool — one scheduler core behind
|
|
26
|
+
`ProcessExecutor.run_many`, `ProcessExecutor.open_pool`, and
|
|
27
|
+
`ExecutionPool.run_stream`, with one shared resident bound over running
|
|
28
|
+
and completed-but-undelivered submissions, completion-order delivery,
|
|
29
|
+
backpressure on intake, cancellation, drain, and abort.
|
|
30
|
+
|
|
31
|
+
Execution runs on macOS; the engine refuses other platforms at the
|
|
32
|
+
declaration boundary, and the tests that need real process semantics are
|
|
33
|
+
marked accordingly.
|
|
34
|
+
|
|
35
|
+
- Exact public API: [`src/dr_exec/__init__.py`](src/dr_exec/__init__.py)
|
|
36
|
+
- Stable capability boundaries:
|
|
37
|
+
[`src/dr_exec/protocols.py`](src/dr_exec/protocols.py)
|
|
38
|
+
- Planned behavioral contracts:
|
|
39
|
+
[`docs/v1-plan/contracts.toml`](docs/v1-plan/contracts.toml)
|
|
40
|
+
- Design and qualification mechanics:
|
|
41
|
+
[`docs/v1-plan/v1-design.md`](docs/v1-plan/v1-design.md)
|
|
42
|
+
- Shared serialization capabilities delivered by the dr-serialize pin:
|
|
43
|
+
[`docs/v1-plan/dr-serialize-additions.md`](docs/v1-plan/dr-serialize-additions.md)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dr-exec"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Contract-driven local process execution"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"dr-serialize==0.1.1",
|
|
9
|
+
"dr-store>=0.1.1",
|
|
10
|
+
"pydantic==2.13.4",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[[project.authors]]
|
|
14
|
+
name = "Danielle Rothermel"
|
|
15
|
+
email = "danielle.rothermel@gmail.com"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["uv_build>=0.11.25,<0.12.0"]
|
|
19
|
+
build-backend = "uv_build"
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=8.4.2",
|
|
24
|
+
"ruff>=0.16.1",
|
|
25
|
+
"ty>=0.0.65",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[tool.ruff]
|
|
29
|
+
include = [
|
|
30
|
+
"scripts/**/*.py",
|
|
31
|
+
"src/**/*.py",
|
|
32
|
+
"tests/**/*.py",
|
|
33
|
+
]
|
|
34
|
+
line-length = 79
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
testpaths = ["tests"]
|
|
38
|
+
addopts = "--strict-markers --strict-config"
|
|
39
|
+
|
|
40
|
+
[tool.ty.src]
|
|
41
|
+
include = [
|
|
42
|
+
"scripts",
|
|
43
|
+
"src",
|
|
44
|
+
"tests",
|
|
45
|
+
]
|
|
46
|
+
exclude = ["nbs"]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dr-exec"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Contract-driven local process execution"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Danielle Rothermel", email = "danielle.rothermel@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"dr-serialize==0.1.1",
|
|
12
|
+
"dr-store>=0.1.1",
|
|
13
|
+
"pydantic==2.13.4",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.11.25,<0.12.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
19
|
+
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = [
|
|
22
|
+
"pytest>=8.4.2",
|
|
23
|
+
"ruff>=0.16.1",
|
|
24
|
+
"ty>=0.0.65",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.ruff]
|
|
28
|
+
include = ["scripts/**/*.py", "src/**/*.py", "tests/**/*.py"]
|
|
29
|
+
line-length = 79
|
|
30
|
+
|
|
31
|
+
[tool.pytest.ini_options]
|
|
32
|
+
testpaths = ["tests"]
|
|
33
|
+
addopts = "--strict-markers --strict-config"
|
|
34
|
+
|
|
35
|
+
[tool.ty.src]
|
|
36
|
+
include = ["scripts", "src", "tests"]
|
|
37
|
+
exclude = ["nbs"]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Public dr-exec v1 capability and data-contract surface."""
|
|
2
|
+
|
|
3
|
+
from dr_exec.cancel import CancelToken
|
|
4
|
+
from dr_exec.declare import (
|
|
5
|
+
Budgets,
|
|
6
|
+
ByteBudget,
|
|
7
|
+
CountBudget,
|
|
8
|
+
DurationBudget,
|
|
9
|
+
EnvGrant,
|
|
10
|
+
EnvGrantRecord,
|
|
11
|
+
EnvVar,
|
|
12
|
+
ExecutionJob,
|
|
13
|
+
ExecutionTarget,
|
|
14
|
+
ExecutorSelfBudgets,
|
|
15
|
+
FiniteByteLimit,
|
|
16
|
+
FiniteCountLimit,
|
|
17
|
+
FiniteDurationLimit,
|
|
18
|
+
FiniteOutput,
|
|
19
|
+
OutputBudget,
|
|
20
|
+
PayloadRetentionBudget,
|
|
21
|
+
StreamRetentionBudget,
|
|
22
|
+
TrustedCommandTarget,
|
|
23
|
+
UnbudgetedLimit,
|
|
24
|
+
UnbudgetedOutput,
|
|
25
|
+
UntrustedCommandTarget,
|
|
26
|
+
UntrustedPythonTarget,
|
|
27
|
+
)
|
|
28
|
+
from dr_exec.errors import DeclarationError, ExecutorFailure, RecordLoadError
|
|
29
|
+
from dr_exec.executor import ProcessExecutor
|
|
30
|
+
from dr_exec.fake import FakeExecutor
|
|
31
|
+
from dr_exec.kinds import (
|
|
32
|
+
BudgetAxis,
|
|
33
|
+
CapacitySource,
|
|
34
|
+
ContainmentProfile,
|
|
35
|
+
EnvGrantKind,
|
|
36
|
+
ExecutionPoolState,
|
|
37
|
+
ExecutionTargetKind,
|
|
38
|
+
FailureOwner,
|
|
39
|
+
LimitKind,
|
|
40
|
+
OutcomeKind,
|
|
41
|
+
OutputOverflowPolicy,
|
|
42
|
+
ProtocolFailureCode,
|
|
43
|
+
RecordReceiptKind,
|
|
44
|
+
RecordState,
|
|
45
|
+
RuntimeKind,
|
|
46
|
+
)
|
|
47
|
+
from dr_exec.names import AttemptId, ExecutionId, JobId
|
|
48
|
+
from dr_exec.pool import (
|
|
49
|
+
AutoPoolCapacity,
|
|
50
|
+
EffectivePoolCapacity,
|
|
51
|
+
ExecutionCompletion,
|
|
52
|
+
ExecutionPool,
|
|
53
|
+
ExecutionPoolConfig,
|
|
54
|
+
ExecutionSubmission,
|
|
55
|
+
FixedPoolCapacity,
|
|
56
|
+
PoolCapacity,
|
|
57
|
+
)
|
|
58
|
+
from dr_exec.protocols import Executor, RunStore, Runtime
|
|
59
|
+
from dr_exec.record import (
|
|
60
|
+
BudgetExceededOutcome,
|
|
61
|
+
BudgetExceededOutcomeRecord,
|
|
62
|
+
CancelledOutcome,
|
|
63
|
+
CancelledOutcomeRecord,
|
|
64
|
+
CompletedExecution,
|
|
65
|
+
CompleteRecordReceipt,
|
|
66
|
+
DegradedRecordReceipt,
|
|
67
|
+
ExecutionAttribution,
|
|
68
|
+
ExecutionAttributionRecord,
|
|
69
|
+
ExecutionMeasurements,
|
|
70
|
+
ExecutionOutcome,
|
|
71
|
+
ExecutionOutcomeRecord,
|
|
72
|
+
ExecutionResult,
|
|
73
|
+
ExecutionResultRecord,
|
|
74
|
+
ExecutionTargetRecord,
|
|
75
|
+
ExitedOutcome,
|
|
76
|
+
ExitedOutcomeRecord,
|
|
77
|
+
FakeRecordReceipt,
|
|
78
|
+
FinalizedRecord,
|
|
79
|
+
OutputArtifactRecord,
|
|
80
|
+
OutputArtifactRecords,
|
|
81
|
+
PayloadOutputRecords,
|
|
82
|
+
PayloadOutputs,
|
|
83
|
+
PreparedRecord,
|
|
84
|
+
ProcessRecord,
|
|
85
|
+
ProtocolFailedOutcome,
|
|
86
|
+
ProtocolFailedOutcomeRecord,
|
|
87
|
+
RealRecordReceipt,
|
|
88
|
+
RecordingFailure,
|
|
89
|
+
RecordReceipt,
|
|
90
|
+
RetainedPayloadStream,
|
|
91
|
+
RetainedPayloadStreamRecord,
|
|
92
|
+
RunDeclaration,
|
|
93
|
+
RunningRecord,
|
|
94
|
+
RunRecord,
|
|
95
|
+
RunRecordHeader,
|
|
96
|
+
SignaledOutcome,
|
|
97
|
+
SignaledOutcomeRecord,
|
|
98
|
+
SpawnAbsentOutcome,
|
|
99
|
+
SpawnAbsentOutcomeRecord,
|
|
100
|
+
SpawnFailedOutcome,
|
|
101
|
+
SpawnFailedOutcomeRecord,
|
|
102
|
+
TrustedCommandTargetRecord,
|
|
103
|
+
UntrustedCommandTargetRecord,
|
|
104
|
+
UntrustedPythonTargetRecord,
|
|
105
|
+
)
|
|
106
|
+
from dr_exec.runtime import (
|
|
107
|
+
IsolatedHostPythonRuntime,
|
|
108
|
+
PreparedPythonProcess,
|
|
109
|
+
RuntimeRecord,
|
|
110
|
+
)
|
|
111
|
+
from dr_exec.store import (
|
|
112
|
+
DirectoryRunStore,
|
|
113
|
+
FinalizableRun,
|
|
114
|
+
PreparedRun,
|
|
115
|
+
RunningRun,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
__all__ = [
|
|
119
|
+
"AttemptId",
|
|
120
|
+
"AutoPoolCapacity",
|
|
121
|
+
"BudgetAxis",
|
|
122
|
+
"BudgetExceededOutcome",
|
|
123
|
+
"BudgetExceededOutcomeRecord",
|
|
124
|
+
"Budgets",
|
|
125
|
+
"ByteBudget",
|
|
126
|
+
"CancelToken",
|
|
127
|
+
"CancelledOutcome",
|
|
128
|
+
"CancelledOutcomeRecord",
|
|
129
|
+
"CapacitySource",
|
|
130
|
+
"CompleteRecordReceipt",
|
|
131
|
+
"CompletedExecution",
|
|
132
|
+
"ContainmentProfile",
|
|
133
|
+
"CountBudget",
|
|
134
|
+
"DeclarationError",
|
|
135
|
+
"DegradedRecordReceipt",
|
|
136
|
+
"DirectoryRunStore",
|
|
137
|
+
"DurationBudget",
|
|
138
|
+
"EffectivePoolCapacity",
|
|
139
|
+
"EnvGrant",
|
|
140
|
+
"EnvGrantKind",
|
|
141
|
+
"EnvGrantRecord",
|
|
142
|
+
"EnvVar",
|
|
143
|
+
"ExecutionAttribution",
|
|
144
|
+
"ExecutionAttributionRecord",
|
|
145
|
+
"ExecutionCompletion",
|
|
146
|
+
"ExecutionId",
|
|
147
|
+
"ExecutionJob",
|
|
148
|
+
"ExecutionMeasurements",
|
|
149
|
+
"ExecutionOutcome",
|
|
150
|
+
"ExecutionOutcomeRecord",
|
|
151
|
+
"ExecutionPool",
|
|
152
|
+
"ExecutionPoolConfig",
|
|
153
|
+
"ExecutionPoolState",
|
|
154
|
+
"ExecutionResult",
|
|
155
|
+
"ExecutionResultRecord",
|
|
156
|
+
"ExecutionSubmission",
|
|
157
|
+
"ExecutionTarget",
|
|
158
|
+
"ExecutionTargetKind",
|
|
159
|
+
"ExecutionTargetRecord",
|
|
160
|
+
"Executor",
|
|
161
|
+
"ExecutorFailure",
|
|
162
|
+
"ExecutorSelfBudgets",
|
|
163
|
+
"ExitedOutcome",
|
|
164
|
+
"ExitedOutcomeRecord",
|
|
165
|
+
"FailureOwner",
|
|
166
|
+
"FakeExecutor",
|
|
167
|
+
"FakeRecordReceipt",
|
|
168
|
+
"FinalizableRun",
|
|
169
|
+
"FinalizedRecord",
|
|
170
|
+
"FiniteByteLimit",
|
|
171
|
+
"FiniteCountLimit",
|
|
172
|
+
"FiniteDurationLimit",
|
|
173
|
+
"FiniteOutput",
|
|
174
|
+
"FixedPoolCapacity",
|
|
175
|
+
"IsolatedHostPythonRuntime",
|
|
176
|
+
"JobId",
|
|
177
|
+
"LimitKind",
|
|
178
|
+
"OutcomeKind",
|
|
179
|
+
"OutputArtifactRecord",
|
|
180
|
+
"OutputArtifactRecords",
|
|
181
|
+
"OutputBudget",
|
|
182
|
+
"OutputOverflowPolicy",
|
|
183
|
+
"PayloadOutputRecords",
|
|
184
|
+
"PayloadOutputs",
|
|
185
|
+
"PayloadRetentionBudget",
|
|
186
|
+
"PoolCapacity",
|
|
187
|
+
"PreparedPythonProcess",
|
|
188
|
+
"PreparedRecord",
|
|
189
|
+
"PreparedRun",
|
|
190
|
+
"ProcessExecutor",
|
|
191
|
+
"ProcessRecord",
|
|
192
|
+
"ProtocolFailedOutcome",
|
|
193
|
+
"ProtocolFailedOutcomeRecord",
|
|
194
|
+
"ProtocolFailureCode",
|
|
195
|
+
"RealRecordReceipt",
|
|
196
|
+
"RecordLoadError",
|
|
197
|
+
"RecordReceipt",
|
|
198
|
+
"RecordReceiptKind",
|
|
199
|
+
"RecordState",
|
|
200
|
+
"RecordingFailure",
|
|
201
|
+
"RetainedPayloadStream",
|
|
202
|
+
"RetainedPayloadStreamRecord",
|
|
203
|
+
"RunDeclaration",
|
|
204
|
+
"RunRecord",
|
|
205
|
+
"RunRecordHeader",
|
|
206
|
+
"RunStore",
|
|
207
|
+
"RunningRecord",
|
|
208
|
+
"RunningRun",
|
|
209
|
+
"Runtime",
|
|
210
|
+
"RuntimeKind",
|
|
211
|
+
"RuntimeRecord",
|
|
212
|
+
"SignaledOutcome",
|
|
213
|
+
"SignaledOutcomeRecord",
|
|
214
|
+
"SpawnAbsentOutcome",
|
|
215
|
+
"SpawnAbsentOutcomeRecord",
|
|
216
|
+
"SpawnFailedOutcome",
|
|
217
|
+
"SpawnFailedOutcomeRecord",
|
|
218
|
+
"StreamRetentionBudget",
|
|
219
|
+
"TrustedCommandTarget",
|
|
220
|
+
"TrustedCommandTargetRecord",
|
|
221
|
+
"UnbudgetedLimit",
|
|
222
|
+
"UnbudgetedOutput",
|
|
223
|
+
"UntrustedCommandTarget",
|
|
224
|
+
"UntrustedCommandTargetRecord",
|
|
225
|
+
"UntrustedPythonTarget",
|
|
226
|
+
"UntrustedPythonTargetRecord",
|
|
227
|
+
]
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Fixed isolated invocation shape and the library-owned child wrapper.
|
|
2
|
+
|
|
3
|
+
The interpreter is always invoked as ``<executable> -I -c <source>``. The
|
|
4
|
+
consumer's ``driver_source`` never reaches argv, a shell, or an import
|
|
5
|
+
path: the library-owned wrapper carries it as one embedded string literal
|
|
6
|
+
bound to a fixed name.
|
|
7
|
+
|
|
8
|
+
The wrapper runs inside an isolated host interpreter that is not required
|
|
9
|
+
to have dr-exec importable, so its body is stdlib-only and reproduces the
|
|
10
|
+
pinned canonical JSON profile directly. It opens the protected descriptor
|
|
11
|
+
before any domain code, reads the request through EOF, validates it,
|
|
12
|
+
resolves ``dr_exec_main``, and writes LF-terminated canonical frames.
|
|
13
|
+
|
|
14
|
+
Failure ownership is split at the wrapper boundary. A missing or
|
|
15
|
+
non-callable entrypoint, a source-load failure, and a callback failure are
|
|
16
|
+
payload-owned: the wrapper stops without a completion frame, so the parent
|
|
17
|
+
observes an incomplete stream with every previously accepted output
|
|
18
|
+
preserved. A protected-writer failure is executor-owned machinery failure.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
# Child-observable literals. The invocation shape, the entrypoint name,
|
|
24
|
+
# the embedded-source binding, and the protected descriptor number are
|
|
25
|
+
# pinned; changing any of them is a standing-contract revision, not an
|
|
26
|
+
# implementation detail.
|
|
27
|
+
ISOLATED_INVOCATION_ARGUMENTS = ("-I", "-c")
|
|
28
|
+
DRIVER_ENTRYPOINT_NAME = "dr_exec_main"
|
|
29
|
+
DRIVER_SOURCE_BINDING = "DR_EXEC_DRIVER_SOURCE"
|
|
30
|
+
PROTOCOL_DESCRIPTOR = 3
|
|
31
|
+
|
|
32
|
+
# The wrapper body, stdlib-only and self-contained. It reads its own
|
|
33
|
+
# module globals and never inspects argv or the environment. The pinned
|
|
34
|
+
# child-observable literals are not spelled here: they are rendered from
|
|
35
|
+
# the module constants above, which are their single source.
|
|
36
|
+
_WRAPPER_BODY = '''
|
|
37
|
+
import hashlib as _dr_exec_hashlib
|
|
38
|
+
import json as _dr_exec_json
|
|
39
|
+
import os as _dr_exec_os
|
|
40
|
+
import sys as _dr_exec_sys
|
|
41
|
+
|
|
42
|
+
_DR_EXEC_IDENTITY_FIELDS = {"schema", "schema_version", "payload"}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _dr_exec_canonical(value):
|
|
46
|
+
"""Render the pinned canonical JSON profile as exact UTF-8 bytes."""
|
|
47
|
+
return _dr_exec_json.dumps(
|
|
48
|
+
value,
|
|
49
|
+
sort_keys=True,
|
|
50
|
+
separators=(",", ":"),
|
|
51
|
+
ensure_ascii=True,
|
|
52
|
+
allow_nan=False,
|
|
53
|
+
).encode("utf-8")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _dr_exec_strict(value, path):
|
|
57
|
+
"""Accept only strict JSON values; reject anything else by path."""
|
|
58
|
+
if value is None or isinstance(value, (str, bool, int)):
|
|
59
|
+
return
|
|
60
|
+
if isinstance(value, float):
|
|
61
|
+
if value != value or value in (float("inf"), float("-inf")):
|
|
62
|
+
raise ValueError("non-finite number at " + path)
|
|
63
|
+
return
|
|
64
|
+
if isinstance(value, list):
|
|
65
|
+
for index, item in enumerate(value):
|
|
66
|
+
_dr_exec_strict(item, path + "[" + str(index) + "]")
|
|
67
|
+
return
|
|
68
|
+
if isinstance(value, dict):
|
|
69
|
+
for key, item in value.items():
|
|
70
|
+
if not isinstance(key, str):
|
|
71
|
+
raise ValueError("non-string key at " + path)
|
|
72
|
+
_dr_exec_strict(item, path + "." + key)
|
|
73
|
+
return
|
|
74
|
+
raise ValueError("unsupported value at " + path)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _dr_exec_identity(document, origin):
|
|
78
|
+
"""Validate the exact three-field Identity Document shape."""
|
|
79
|
+
if not isinstance(document, dict):
|
|
80
|
+
raise ValueError(origin + " must be an object")
|
|
81
|
+
if set(document) != _DR_EXEC_IDENTITY_FIELDS:
|
|
82
|
+
raise ValueError(origin + " must have exactly the identity fields")
|
|
83
|
+
if not isinstance(document["schema"], str):
|
|
84
|
+
raise ValueError(origin + " schema must be a string")
|
|
85
|
+
version = document["schema_version"]
|
|
86
|
+
if isinstance(version, bool) or not isinstance(version, int):
|
|
87
|
+
raise ValueError(origin + " schema_version must be an integer")
|
|
88
|
+
_dr_exec_strict(document["payload"], origin + ".payload")
|
|
89
|
+
return document
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _dr_exec_read_request():
|
|
93
|
+
"""Read stdin through EOF and validate the canonical request."""
|
|
94
|
+
data = _dr_exec_sys.stdin.buffer.read()
|
|
95
|
+
try:
|
|
96
|
+
decoded = _dr_exec_json.loads(data.decode("utf-8"))
|
|
97
|
+
except (UnicodeDecodeError, ValueError) as error:
|
|
98
|
+
raise ValueError("request is not strict JSON") from error
|
|
99
|
+
document = _dr_exec_identity(decoded, "request")
|
|
100
|
+
if _dr_exec_canonical(document) != data:
|
|
101
|
+
raise ValueError("request bytes are not canonical JSON bytes")
|
|
102
|
+
return document, data
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class _DrExecProtocolWriter:
|
|
106
|
+
"""The protected fd 3 writer, owned by the library, not the payload.
|
|
107
|
+
|
|
108
|
+
The descriptor is duplicated and the original closed at construction,
|
|
109
|
+
so the handle survives domain code that replaces language-level
|
|
110
|
+
stdout or stderr, and the payload cannot reach the protected stream
|
|
111
|
+
through the well-known descriptor number. Every frame is flushed
|
|
112
|
+
before its call returns, so a later payload crash cannot lose an
|
|
113
|
+
output the parent already accepted.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(self, descriptor):
|
|
117
|
+
self._stream = _dr_exec_os.fdopen(_dr_exec_os.dup(descriptor), "wb")
|
|
118
|
+
_dr_exec_os.close(descriptor)
|
|
119
|
+
self._sequence = 0
|
|
120
|
+
self._closed = False
|
|
121
|
+
|
|
122
|
+
def prelude(self, request_bytes):
|
|
123
|
+
digest = _dr_exec_hashlib.sha256(request_bytes).hexdigest()
|
|
124
|
+
self._frame({
|
|
125
|
+
"version": 1,
|
|
126
|
+
"kind": "prelude",
|
|
127
|
+
"request_id_sha256": digest,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
def emit(self, document):
|
|
131
|
+
validated = _dr_exec_identity(document, "output document")
|
|
132
|
+
self._frame({
|
|
133
|
+
"version": 1,
|
|
134
|
+
"kind": "output",
|
|
135
|
+
"sequence": self._sequence,
|
|
136
|
+
"document": {
|
|
137
|
+
"schema": validated["schema"],
|
|
138
|
+
"schema_version": validated["schema_version"],
|
|
139
|
+
"payload": validated["payload"],
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
self._sequence += 1
|
|
143
|
+
|
|
144
|
+
def complete(self):
|
|
145
|
+
self._frame({
|
|
146
|
+
"version": 1,
|
|
147
|
+
"kind": "complete",
|
|
148
|
+
"output_count": self._sequence,
|
|
149
|
+
})
|
|
150
|
+
self._closed = True
|
|
151
|
+
self._stream.close()
|
|
152
|
+
|
|
153
|
+
def _frame(self, frame):
|
|
154
|
+
if self._closed:
|
|
155
|
+
raise ValueError("the protected stream is already complete")
|
|
156
|
+
self._stream.write(_dr_exec_canonical(frame) + b"\\n")
|
|
157
|
+
self._stream.flush()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _dr_exec_bootstrap():
|
|
161
|
+
writer = _DrExecProtocolWriter(_DR_EXEC_PROTOCOL_DESCRIPTOR)
|
|
162
|
+
request, request_bytes = _dr_exec_read_request()
|
|
163
|
+
writer.prelude(request_bytes)
|
|
164
|
+
namespace = {"__name__": "dr_exec_driver"}
|
|
165
|
+
exec(DR_EXEC_DRIVER_SOURCE, namespace)
|
|
166
|
+
entrypoint = namespace.get(_DR_EXEC_ENTRYPOINT_NAME)
|
|
167
|
+
if not callable(entrypoint):
|
|
168
|
+
raise ValueError(
|
|
169
|
+
"driver_source must define a callable " + _DR_EXEC_ENTRYPOINT_NAME
|
|
170
|
+
)
|
|
171
|
+
entrypoint(request, writer.emit)
|
|
172
|
+
writer.complete()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
_dr_exec_bootstrap()
|
|
176
|
+
'''
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def driver_wrapper_source(driver_source: str, /) -> str:
|
|
180
|
+
"""Return the library-owned wrapper source embedding ``driver_source``.
|
|
181
|
+
|
|
182
|
+
``driver_source`` is embedded through ``repr``, so arbitrary consumer
|
|
183
|
+
text -- quotes, backslashes, and newlines included -- stays one inert
|
|
184
|
+
string literal in the wrapper rather than executable wrapper syntax.
|
|
185
|
+
The pinned child-observable literals are rendered from the module
|
|
186
|
+
constants, so the child can only ever observe their one spelling.
|
|
187
|
+
"""
|
|
188
|
+
if "\0" in driver_source:
|
|
189
|
+
raise ValueError("driver_source must not contain NUL")
|
|
190
|
+
return "\n".join(
|
|
191
|
+
(
|
|
192
|
+
f"{DRIVER_SOURCE_BINDING} = {driver_source!r}",
|
|
193
|
+
f"_DR_EXEC_PROTOCOL_DESCRIPTOR = {PROTOCOL_DESCRIPTOR}",
|
|
194
|
+
f"_DR_EXEC_ENTRYPOINT_NAME = {DRIVER_ENTRYPOINT_NAME!r}",
|
|
195
|
+
_WRAPPER_BODY,
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
__all__ = [
|
|
201
|
+
"DRIVER_ENTRYPOINT_NAME",
|
|
202
|
+
"DRIVER_SOURCE_BINDING",
|
|
203
|
+
"ISOLATED_INVOCATION_ARGUMENTS",
|
|
204
|
+
"PROTOCOL_DESCRIPTOR",
|
|
205
|
+
"driver_wrapper_source",
|
|
206
|
+
]
|