@tangle-network/agent-bench 0.1.0 → 0.3.5
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.
- package/CHANGELOG.md +17 -0
- package/HARNESS.md +302 -0
- package/README.md +26 -1
- package/fixtures/aec-bench.json +18 -0
- package/fixtures/agentbench-dbbench.json +22 -0
- package/fixtures/bfcl.json +45 -0
- package/fixtures/commit0.json +72 -0
- package/fixtures/crag.json +10 -0
- package/fixtures/dabstep.json +22 -0
- package/fixtures/enterpriseops-gym.json +103 -0
- package/fixtures/finresearchbench.json +21 -0
- package/fixtures/finsearchcomp.json +66 -0
- package/fixtures/frames.json +26 -0
- package/fixtures/hotpotqa.json +182 -0
- package/fixtures/nomiracl.json +26 -0
- package/fixtures/open-rag-bench.json +16 -0
- package/fixtures/pier-agent/no-model-task/environment/Dockerfile +16 -0
- package/fixtures/pier-agent/no-model-task/environment/seed/src/status.txt +1 -0
- package/fixtures/pier-agent/no-model-task/instruction.md +6 -0
- package/fixtures/pier-agent/no-model-task/pre_artifacts.sh +6 -0
- package/fixtures/pier-agent/no-model-task/task.toml +35 -0
- package/fixtures/pier-agent/no-model-task/tests/Dockerfile +17 -0
- package/fixtures/pier-agent/no-model-task/tests/seed/src/status.txt +1 -0
- package/fixtures/pier-agent/no-model-task/tests/test.sh +19 -0
- package/fixtures/programbench.json +17 -0
- package/fixtures/ragbench.json +21 -0
- package/fixtures/simpleqa.json +121 -0
- package/fixtures/t2-ragbench.json +13 -0
- package/fixtures/tau2-bench.json +16 -0
- package/fixtures/tau3-banking.json +16 -0
- package/fixtures/toollm.json +28 -0
- package/fixtures/webarena-verified.json +20 -0
- package/package.json +39 -15
- package/pier_agents/__init__.py +18 -0
- package/pier_agents/candidate_contract.py +755 -0
- package/pier_agents/process_boundary.py +321 -0
- package/pier_agents/tangle_candidate.py +907 -0
- package/pier_agents/workspace_boundary.py +368 -0
- package/scripts/appworld_driver.py +359 -0
- package/scripts/cadbench_prepare.py +22 -0
- package/scripts/cadgenbench_hard_parts.py +48 -0
- package/scripts/clbench_codebase_judge.py +73 -0
- package/scripts/commit0_judge.py +170 -0
- package/scripts/dabstep_judge.py +42 -0
- package/scripts/enterpriseops_gym_judge.py +281 -0
- package/scripts/programbench_judge.py +120 -0
- package/scripts/render-gate-chart.mjs +176 -0
- package/scripts/run-package-tests.mjs +56 -0
- package/scripts/terminate-pier-trial.mts +66 -0
- package/scripts/trata-hedge/README.md +56 -0
- package/scripts/trata-hedge/run.sh +60 -0
- package/scripts/trata-hedge/solve.py +83 -0
- package/scripts/verify-packed-consumer.mjs +224 -0
- package/scripts/verify-pier-agent.mts +715 -0
- package/scripts/verify-pier-pair.mts +74 -0
- package/scripts/verify-pier-recovery.mts +139 -0
- package/src/adapters.ts +26 -0
- package/src/benchmarks/_harness.test.mts +178 -0
- package/src/benchmarks/_harness.ts +239 -16
- package/src/benchmarks/agentbench.ts +163 -0
- package/src/benchmarks/appworld.test.mts +15 -9
- package/src/benchmarks/bfcl.ts +346 -0
- package/src/benchmarks/crag.ts +137 -0
- package/src/benchmarks/dabstep.test.mts +70 -0
- package/src/benchmarks/dabstep.ts +212 -0
- package/src/benchmarks/external-adapters.test.mts +150 -0
- package/src/benchmarks/finresearchbench.ts +269 -0
- package/src/benchmarks/humaneval.ts +20 -8
- package/src/benchmarks/nomiracl.ts +180 -0
- package/src/benchmarks/open-rag-bench.ts +153 -0
- package/src/benchmarks/rag-benchmarks.test.mts +138 -0
- package/src/benchmarks/rag-shared.ts +327 -0
- package/src/benchmarks/ragbench.ts +171 -0
- package/src/benchmarks/swe-bench.test.mts +61 -0
- package/src/benchmarks/swe-bench.ts +201 -19
- package/src/benchmarks/t2-ragbench.ts +166 -0
- package/src/benchmarks/tau-bench-shared.ts +214 -0
- package/src/benchmarks/tau2-bench.ts +30 -0
- package/src/benchmarks/tau3-banking.ts +29 -0
- package/src/benchmarks/terminal-bench.test.mts +33 -0
- package/src/benchmarks/terminal-bench.ts +23 -8
- package/src/benchmarks/toollm.ts +254 -0
- package/src/benchmarks/types.ts +42 -0
- package/src/benchmarks/webarena-verified.ts +200 -0
- package/src/commit0-prereqs.sh +0 -0
- package/src/coordination-mcp-container-reach.mts +181 -0
- package/src/decoder-live.mts +1 -1
- package/src/examples/README.md +103 -39
- package/src/examples/benchmark-matrix.mts +101 -0
- package/src/examples/lean-proof-gate.README.md +77 -0
- package/src/examples/lean-proof-gate.mts +162 -0
- package/src/examples/lean-verify.ts +95 -0
- package/src/examples/lean.Dockerfile +12 -0
- package/src/examples/math-demo.mts +9 -7
- package/src/examples/strategy-demo.mts +10 -12
- package/src/gate.ts +3 -2
- package/src/hev-eval.mts +69 -0
- package/src/hev-improve.mts +169 -0
- package/src/hev-structural.mts +688 -0
- package/src/index.ts +73 -0
- package/src/mbpp-structural.mts +662 -0
- package/src/pier-agent.test-fixtures.mts +19 -0
- package/src/pier-agent.test.mts +363 -0
- package/src/pier-agent.ts +657 -0
- package/src/pier-result-grader.mjs +30 -0
- package/src/pier-result-grader.test.mts +62 -0
- package/src/pier-result-grader.ts +108 -0
- package/src/pier-task-outcome.test.mts +117 -0
- package/src/pier-task-outcome.ts +240 -0
- package/src/pier-trial-controller.test.mts +412 -0
- package/src/pier-trial-controller.ts +858 -0
- package/src/pier-trial-supervisor.mjs +352 -0
- package/src/resolve-client.ts +25 -2
- package/src/run-benchmarks-cli.mts +72 -0
- package/src/run-benchmarks-report.ts +66 -0
- package/src/run-benchmarks.test.mts +231 -0
- package/src/run-benchmarks.ts +589 -0
- package/src/smoke-structural-rollout.mts +393 -0
- package/src/swe-bench-env.test.ts +207 -0
- package/src/swe-bench-env.ts +554 -0
- package/src/swe-jail.ts +293 -0
- package/src/swe-self-improve.mts +84 -0
- package/src/swe-structural-judge-policy.test.ts +117 -0
- package/src/swe-structural-judge-policy.ts +133 -0
- package/src/swe-structural-policy.test.ts +124 -0
- package/src/swe-structural-policy.ts +132 -0
- package/src/swe-structural-provenance.test.ts +93 -0
- package/src/swe-structural-provenance.ts +138 -0
- package/src/swe-structural.mts +1260 -0
- package/src/swe-temp.ts +14 -0
- package/src/tb-container-executor.mts +234 -0
- package/src/tb-container-executor.test.mts +99 -0
- package/src/tb-supervisor-sidecar.mts +222 -0
- package/src/trata-gepa.mts +1 -1
- package/steerers/eops-itsm-population.json +1 -0
- package/tb_agents/opencode_refine_agent.py +117 -0
- package/tb_agents/opencode_router_agent.py +406 -0
- package/tb_agents/opencode_supervisor_agent.py +239 -0
- package/tb_agents/script_agent.py +66 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""Verify and materialize runtime-signed workspaces inside a Pier container."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import secrets
|
|
6
|
+
import shlex
|
|
7
|
+
import tempfile
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path, PurePosixPath
|
|
10
|
+
from typing import Any, Protocol
|
|
11
|
+
|
|
12
|
+
from pier.environments.base import BaseEnvironment
|
|
13
|
+
|
|
14
|
+
from .candidate_contract import (
|
|
15
|
+
PreparedCandidateContract,
|
|
16
|
+
WorkspaceFile,
|
|
17
|
+
sha256_bytes,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _workspace_check_script(
|
|
22
|
+
root: str,
|
|
23
|
+
files: tuple[WorkspaceFile, ...],
|
|
24
|
+
*,
|
|
25
|
+
allow_task_metadata: bool,
|
|
26
|
+
) -> bytes:
|
|
27
|
+
quoted_root = shlex.quote(root)
|
|
28
|
+
prune = (
|
|
29
|
+
f"\\( -path {shlex.quote(root + '/.git')} -o "
|
|
30
|
+
f"-path {shlex.quote(root + '/.sidecar')} \\) -prune -o "
|
|
31
|
+
if allow_task_metadata
|
|
32
|
+
else ""
|
|
33
|
+
)
|
|
34
|
+
expected_args = " ".join(shlex.quote(file.path) for file in files)
|
|
35
|
+
expected = (
|
|
36
|
+
f"$(printf '%s\\n' {expected_args} | LC_ALL=C sort)" if files else "''"
|
|
37
|
+
)
|
|
38
|
+
checks = [
|
|
39
|
+
"#!/bin/sh",
|
|
40
|
+
"set -eu",
|
|
41
|
+
f"test -d {quoted_root}",
|
|
42
|
+
f"test ! -L {quoted_root}",
|
|
43
|
+
f'test "$(realpath -e -- {quoted_root})" = {quoted_root}',
|
|
44
|
+
f'test -z "$(find {quoted_root} {prune}-type l -print -quit)"',
|
|
45
|
+
f'test -z "$(find {quoted_root} {prune}! -type d ! -type f -print -quit)"',
|
|
46
|
+
f"observed=$(find {quoted_root} {prune}-type f -printf '%P\\n' | LC_ALL=C sort)",
|
|
47
|
+
f"expected={expected}",
|
|
48
|
+
'test "$observed" = "$expected"',
|
|
49
|
+
]
|
|
50
|
+
for file in files:
|
|
51
|
+
path = shlex.quote(f"{root}/{file.path}")
|
|
52
|
+
checks.extend(
|
|
53
|
+
[
|
|
54
|
+
f"test -f {path}",
|
|
55
|
+
f"test ! -L {path}",
|
|
56
|
+
f'test "$(stat -c %h -- {path})" = 1',
|
|
57
|
+
f'test "$(stat -c %a -- {path})" = {file.mode:o}',
|
|
58
|
+
f'test "$(wc -c < {path})" = {file.byte_length}',
|
|
59
|
+
f"test \"$(sha256sum -- {path} | cut -d' ' -f1)\" = "
|
|
60
|
+
f"{file.sha256.removeprefix('sha256:')}",
|
|
61
|
+
]
|
|
62
|
+
)
|
|
63
|
+
return ("\n".join(checks) + "\n").encode("utf-8")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class WorkspaceBoundaryHost(Protocol):
|
|
67
|
+
async def _exec(
|
|
68
|
+
self, environment: BaseEnvironment, command: str, **kwargs: Any
|
|
69
|
+
) -> Any: ...
|
|
70
|
+
|
|
71
|
+
async def _ensure_real_directory(
|
|
72
|
+
self,
|
|
73
|
+
environment: BaseEnvironment,
|
|
74
|
+
path: str,
|
|
75
|
+
*,
|
|
76
|
+
anchor: str,
|
|
77
|
+
mode: int = 0o755,
|
|
78
|
+
) -> None: ...
|
|
79
|
+
|
|
80
|
+
async def _assert_real_directory(
|
|
81
|
+
self,
|
|
82
|
+
environment: BaseEnvironment,
|
|
83
|
+
path: str,
|
|
84
|
+
*,
|
|
85
|
+
label: str,
|
|
86
|
+
user: str | int | None = "root",
|
|
87
|
+
) -> None: ...
|
|
88
|
+
|
|
89
|
+
async def _write_verified_file(
|
|
90
|
+
self,
|
|
91
|
+
environment: BaseEnvironment,
|
|
92
|
+
source: Path,
|
|
93
|
+
target: str,
|
|
94
|
+
*,
|
|
95
|
+
anchor: str,
|
|
96
|
+
mode: int,
|
|
97
|
+
digest: str,
|
|
98
|
+
byte_length: int,
|
|
99
|
+
) -> None: ...
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class WorkspaceBoundaryConfig:
|
|
104
|
+
contract: PreparedCandidateContract
|
|
105
|
+
task_snapshot: Path
|
|
106
|
+
control_root: str
|
|
107
|
+
protected_roots: tuple[str, ...]
|
|
108
|
+
error_type: type[Exception]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def candidate_git_env(task_root: str) -> dict[str, str]:
|
|
112
|
+
return {
|
|
113
|
+
"GIT_CONFIG_COUNT": "1",
|
|
114
|
+
"GIT_CONFIG_KEY_0": "safe.directory",
|
|
115
|
+
"GIT_CONFIG_VALUE_0": task_root,
|
|
116
|
+
"GIT_CONFIG_NOSYSTEM": "1",
|
|
117
|
+
"GIT_CONFIG_GLOBAL": "/dev/null",
|
|
118
|
+
"GIT_CONFIG_SYSTEM": "/dev/null",
|
|
119
|
+
"GIT_NO_REPLACE_OBJECTS": "1",
|
|
120
|
+
"GIT_TERMINAL_PROMPT": "0",
|
|
121
|
+
"LC_ALL": "C",
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class CandidateWorkspaceBoundary:
|
|
126
|
+
def __init__(self, host: WorkspaceBoundaryHost, config: WorkspaceBoundaryConfig):
|
|
127
|
+
self._host = host
|
|
128
|
+
self._config = config
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def _contract(self) -> PreparedCandidateContract:
|
|
132
|
+
return self._config.contract
|
|
133
|
+
|
|
134
|
+
def _error(self, message: str) -> Exception:
|
|
135
|
+
return self._config.error_type(message)
|
|
136
|
+
|
|
137
|
+
async def verify_repository(self, environment: BaseEnvironment) -> None:
|
|
138
|
+
root = shlex.quote(self._contract.task_root)
|
|
139
|
+
git = f"git -c core.hooksPath=/dev/null -C {root}"
|
|
140
|
+
env = candidate_git_env(self._contract.task_root)
|
|
141
|
+
head = await self._host._exec(environment, f"{git} rev-parse HEAD", env=env)
|
|
142
|
+
tree = await self._host._exec(
|
|
143
|
+
environment, f"{git} rev-parse 'HEAD^{{tree}}'", env=env
|
|
144
|
+
)
|
|
145
|
+
replacements = await self._host._exec(
|
|
146
|
+
environment,
|
|
147
|
+
f"{git} for-each-ref --format='%(refname)' refs/replace",
|
|
148
|
+
env=env,
|
|
149
|
+
)
|
|
150
|
+
if (head.stdout or "").strip() != self._contract.repository_base_commit:
|
|
151
|
+
raise self._error("task checkout HEAD does not match the signed base commit")
|
|
152
|
+
if (tree.stdout or "").strip() != self._contract.repository_base_tree:
|
|
153
|
+
raise self._error("task checkout tree does not match the signed base tree")
|
|
154
|
+
if (replacements.stdout or "").strip():
|
|
155
|
+
raise self._error("task checkout contains forbidden Git replace refs")
|
|
156
|
+
|
|
157
|
+
async def materialize_task_workspace(self, environment: BaseEnvironment) -> None:
|
|
158
|
+
root = self._contract.task_root
|
|
159
|
+
quoted = shlex.quote(root)
|
|
160
|
+
exists = await self._host._exec(
|
|
161
|
+
environment,
|
|
162
|
+
f"test -e {quoted} || test -L {quoted}",
|
|
163
|
+
user="root",
|
|
164
|
+
check=False,
|
|
165
|
+
)
|
|
166
|
+
if exists.return_code == 0:
|
|
167
|
+
return
|
|
168
|
+
if exists.return_code != 1:
|
|
169
|
+
raise self._error("could not inspect the signed task root")
|
|
170
|
+
|
|
171
|
+
parent = str(PurePosixPath(root).parent)
|
|
172
|
+
await self._host._ensure_real_directory(environment, parent, anchor="/")
|
|
173
|
+
await self._host._exec(
|
|
174
|
+
environment,
|
|
175
|
+
"\n".join(
|
|
176
|
+
[
|
|
177
|
+
"set -eu",
|
|
178
|
+
f"test ! -e {quoted}",
|
|
179
|
+
f"test ! -L {quoted}",
|
|
180
|
+
f"mkdir -- {quoted}",
|
|
181
|
+
f'test "$(realpath -e -- {quoted})" = {quoted}',
|
|
182
|
+
]
|
|
183
|
+
),
|
|
184
|
+
user="root",
|
|
185
|
+
)
|
|
186
|
+
await environment.upload_dir(self._config.task_snapshot, root)
|
|
187
|
+
git = f"git -c core.hooksPath=/dev/null -C {quoted}"
|
|
188
|
+
await self._host._exec(
|
|
189
|
+
environment,
|
|
190
|
+
"\n".join(
|
|
191
|
+
[
|
|
192
|
+
"set -eu",
|
|
193
|
+
f"{git} init -b main",
|
|
194
|
+
f"{git} config user.email fixture@tangle.tools",
|
|
195
|
+
f'{git} config user.name "Tangle Fixture"',
|
|
196
|
+
f"{git} add -A",
|
|
197
|
+
f"GIT_AUTHOR_DATE=2000-01-01T00:00:00Z "
|
|
198
|
+
f"GIT_COMMITTER_DATE=2000-01-01T00:00:00Z "
|
|
199
|
+
f"{git} commit -m baseline",
|
|
200
|
+
f"{git} tag benchmark-base",
|
|
201
|
+
]
|
|
202
|
+
),
|
|
203
|
+
env=candidate_git_env(root),
|
|
204
|
+
user="root",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
async def verify_pier_execution_identity(
|
|
208
|
+
self, environment: BaseEnvironment
|
|
209
|
+
) -> None:
|
|
210
|
+
expected = self._contract.container
|
|
211
|
+
configured_image = getattr(environment.task_env_config, "docker_image", None)
|
|
212
|
+
pinned_image = f"{expected['image']}@{expected['indexDigest']}"
|
|
213
|
+
if configured_image != pinned_image:
|
|
214
|
+
raise self._error(
|
|
215
|
+
"Pier task image does not match the signed immutable image: "
|
|
216
|
+
f"expected={pinned_image} observed={configured_image}"
|
|
217
|
+
)
|
|
218
|
+
configured_os = getattr(environment.task_env_config, "os", None)
|
|
219
|
+
configured_os = getattr(configured_os, "value", configured_os)
|
|
220
|
+
if configured_os is not None and str(configured_os).lower() != "linux":
|
|
221
|
+
raise self._error(
|
|
222
|
+
f"Pier task OS does not match signed Linux platform: {configured_os}"
|
|
223
|
+
)
|
|
224
|
+
platform = expected["platform"]
|
|
225
|
+
observed = await self._host._exec(
|
|
226
|
+
environment,
|
|
227
|
+
'printf \'%s %s\' "$(uname -s)" "$(uname -m)"',
|
|
228
|
+
user="root",
|
|
229
|
+
)
|
|
230
|
+
kernel, _, machine = (observed.stdout or "").strip().partition(" ")
|
|
231
|
+
architectures = {"x86_64": "amd64", "aarch64": "arm64"}
|
|
232
|
+
if kernel != "Linux" or architectures.get(machine) != platform["architecture"]:
|
|
233
|
+
raise self._error(
|
|
234
|
+
"running Pier platform does not match the signed container platform: "
|
|
235
|
+
f"observed={kernel}/{machine} expected=linux/{platform['architecture']}"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
async def verify_resolved_root_isolation(
|
|
239
|
+
self, environment: BaseEnvironment
|
|
240
|
+
) -> None:
|
|
241
|
+
roots = [self._contract.task_root]
|
|
242
|
+
if self._contract.candidate_root is not None:
|
|
243
|
+
roots.append(self._contract.candidate_root)
|
|
244
|
+
for root in roots:
|
|
245
|
+
await self._host._assert_real_directory(
|
|
246
|
+
environment, root, label="execution workspace"
|
|
247
|
+
)
|
|
248
|
+
if self._contract.candidate_root is not None:
|
|
249
|
+
task = shlex.quote(self._contract.task_root)
|
|
250
|
+
candidate = shlex.quote(self._contract.candidate_root)
|
|
251
|
+
await self._host._exec(
|
|
252
|
+
environment,
|
|
253
|
+
"\n".join(
|
|
254
|
+
[
|
|
255
|
+
"set -eu",
|
|
256
|
+
f"task=$(realpath -e -- {task})",
|
|
257
|
+
f"candidate=$(realpath -e -- {candidate})",
|
|
258
|
+
'test "$task" != "$candidate"',
|
|
259
|
+
'case "$task" in "$candidate"/*) exit 1;; esac',
|
|
260
|
+
'case "$candidate" in "$task"/*) exit 1;; esac',
|
|
261
|
+
]
|
|
262
|
+
),
|
|
263
|
+
user="root",
|
|
264
|
+
)
|
|
265
|
+
for protected in self._config.protected_roots:
|
|
266
|
+
quoted_protected = shlex.quote(protected)
|
|
267
|
+
for root in roots:
|
|
268
|
+
quoted_root = shlex.quote(root)
|
|
269
|
+
await self._host._exec(
|
|
270
|
+
environment,
|
|
271
|
+
"\n".join(
|
|
272
|
+
[
|
|
273
|
+
"set -eu",
|
|
274
|
+
f"if test -e {quoted_protected}; then",
|
|
275
|
+
f" protected=$(realpath -e -- {quoted_protected})",
|
|
276
|
+
f" root=$(realpath -e -- {quoted_root})",
|
|
277
|
+
' test "$root" != "$protected"',
|
|
278
|
+
' case "$root" in "$protected"/*) exit 1;; esac',
|
|
279
|
+
' case "$protected" in "$root"/*) exit 1;; esac',
|
|
280
|
+
"fi",
|
|
281
|
+
]
|
|
282
|
+
),
|
|
283
|
+
user="root",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
async def verify_container_workspace(
|
|
287
|
+
self,
|
|
288
|
+
environment: BaseEnvironment,
|
|
289
|
+
root: str,
|
|
290
|
+
files: tuple[WorkspaceFile, ...],
|
|
291
|
+
*,
|
|
292
|
+
allow_task_metadata: bool,
|
|
293
|
+
) -> None:
|
|
294
|
+
script = _workspace_check_script(
|
|
295
|
+
root,
|
|
296
|
+
files,
|
|
297
|
+
allow_task_metadata=allow_task_metadata,
|
|
298
|
+
)
|
|
299
|
+
control_root = self._config.control_root
|
|
300
|
+
target = f"{control_root}/workspace-check-{secrets.token_hex(24)}.sh"
|
|
301
|
+
with tempfile.NamedTemporaryFile(delete=False) as handle:
|
|
302
|
+
handle.write(script)
|
|
303
|
+
local_path = Path(handle.name)
|
|
304
|
+
control_ready = False
|
|
305
|
+
|
|
306
|
+
async def cleanup() -> None:
|
|
307
|
+
local_path.unlink(missing_ok=True)
|
|
308
|
+
if not control_ready:
|
|
309
|
+
return
|
|
310
|
+
await self._host._assert_real_directory(
|
|
311
|
+
environment,
|
|
312
|
+
control_root,
|
|
313
|
+
label="candidate control directory",
|
|
314
|
+
)
|
|
315
|
+
await self._host._exec(
|
|
316
|
+
environment,
|
|
317
|
+
f"rm -f -- {shlex.quote(target)}",
|
|
318
|
+
user="root",
|
|
319
|
+
)
|
|
320
|
+
await self._host._exec(
|
|
321
|
+
environment,
|
|
322
|
+
"\n".join(
|
|
323
|
+
[
|
|
324
|
+
"set -eu",
|
|
325
|
+
f"test ! -e {shlex.quote(target)}",
|
|
326
|
+
f"test ! -L {shlex.quote(target)}",
|
|
327
|
+
f'test -z "$(find {shlex.quote(control_root)} -maxdepth 1 '
|
|
328
|
+
"-name '.tangle-*' -print -quit)\"",
|
|
329
|
+
]
|
|
330
|
+
),
|
|
331
|
+
user="root",
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
await self._host._ensure_real_directory(
|
|
336
|
+
environment,
|
|
337
|
+
control_root,
|
|
338
|
+
anchor="/",
|
|
339
|
+
mode=0o700,
|
|
340
|
+
)
|
|
341
|
+
control_ready = True
|
|
342
|
+
await self._host._exec(
|
|
343
|
+
environment,
|
|
344
|
+
f"chmod 0700 -- {shlex.quote(control_root)}",
|
|
345
|
+
user="root",
|
|
346
|
+
)
|
|
347
|
+
await self._host._write_verified_file(
|
|
348
|
+
environment,
|
|
349
|
+
local_path,
|
|
350
|
+
target,
|
|
351
|
+
anchor=control_root,
|
|
352
|
+
mode=0o600,
|
|
353
|
+
digest=sha256_bytes(script),
|
|
354
|
+
byte_length=len(script),
|
|
355
|
+
)
|
|
356
|
+
await self._host._exec(
|
|
357
|
+
environment,
|
|
358
|
+
f"/bin/sh {shlex.quote(target)}",
|
|
359
|
+
user="root",
|
|
360
|
+
)
|
|
361
|
+
except BaseException as primary_error:
|
|
362
|
+
try:
|
|
363
|
+
await cleanup()
|
|
364
|
+
except BaseException as cleanup_error:
|
|
365
|
+
primary_error.add_note(f"workspace check cleanup failed: {cleanup_error}")
|
|
366
|
+
raise
|
|
367
|
+
else:
|
|
368
|
+
await cleanup()
|