mcp-shipyard 0.1.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.
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-shipyard
3
+ Version: 0.1.0
4
+ Summary: Application-independent MCP hosting, payloads, and responses.
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.13
7
+ Description-Content-Type: text/markdown
8
+
9
+ # MCP Shipyard
10
+
11
+ Install the `mcp-shipyard` distribution and import `mcp_shipyard`:
12
+
13
+ ```sh
14
+ python -m pip install mcp-shipyard
15
+ shipyard --help
16
+ ```
17
+
18
+ `mcp_shipyard` manages MCP applications independently of their tool set.
19
+ It contains no Exactus, District, Conjure, FastAPI, or Docker imports.
20
+
21
+ - `models.py`: JSON types, server identity, protocol failures, and outcomes.
22
+ - `payloads.py`: request envelope decoding and validation.
23
+ - `responses.py`: protocol and tool result encoding.
24
+ - `server.py`: negotiation, discovery, authenticated dispatch, and observers.
25
+ - `tools.py`: tool registration and explicit application authorization.
26
+ - `pipelines.py`: pipeline request and executor contracts, and tool adapter.
27
+ - `stdio.py`: bounded newline-delimited transport with caller-owned streams.
28
+ - `environments.py`: approved profiles, enforceable permissions, and disposable
29
+ worker lifecycle shared by providers.
30
+ - `lease_store.py` and `leases.py`: durable ownership, expiration, serialization,
31
+ restart recovery contracts and confirmed worker cleanup.
32
+ - `cli.py` and `__main__.py`: explicit application factory and local principal
33
+ for `python -m mcp_shipyard serve`.
34
+ - `__init__.py`: public API.
35
+ - `features.py`: optional terminal, package-install and resource-relay contracts.
36
+ - `pyproject.toml`: the independently installable `mcp-shipyard` distribution.
37
+
38
+ Applications supply identity, schemas, handlers, and authorization. Monitoring
39
+ is an injected observer; District can consume those events without becoming
40
+ the MCP application's protocol core. Pipeline adapters own worker selection,
41
+ queues, leases, cancellation, and cleanup. The host never interprets arbitrary
42
+ payload values as executable code. Unexpected application errors propagate;
43
+ only explicitly configured safe error types become MCP tool-error content.
44
+
45
+ The Exactus adapter in `mcp/exactus_mcp/mcp_server.py` now uses this shared
46
+ host and registry while retaining its existing tool names and permissions.
47
+ Its environment and REST services remain application adapters; importing this
48
+ library never imports or starts them. Observer parameters are transient and
49
+ must be redacted by a monitor before persistence. Stdio adapters do not close
50
+ streams owned by their callers.
51
+
52
+ Run the independent example from the repository root:
53
+
54
+ ```sh
55
+ python -m mcp_shipyard serve \
56
+ --application examples.tools.mcp_shipyard.echo:create --principal example-user
57
+ ```
58
+
59
+ The application factory and stdio principal are trusted operator configuration.
60
+ They must never be populated directly from remote request arguments.
61
+
62
+ Environment callers choose a registered profile and an argv vector. They
63
+ cannot override that profile's network, privilege, package, or exposure policy.
64
+ Providers must enforce both grants and denials before starting a worker.
65
+ The Conjure Docker adapter currently enforces Linux containers, non-root
66
+ execution, dropped capabilities, no privilege escalation, bounded CPU/memory,
67
+ read-only root, private scratch, bounded output and destruction on every exit.
68
+ Network `none` disables guest networking; `internet` enables Docker bridge
69
+ networking, including reachable private destinations. It is not an Internet-only
70
+ firewall. Allow-list egress, privilege grants, managed package installs, resource
71
+ publishing and interactive shell installation are refused by this adapter.
72
+
73
+ VM providers use the same interface, but no audited Windows or macOS template
74
+ is shipped here. Contract tests for those platform names are not native guest
75
+ execution evidence. A future VM adapter must clone immutable prepared images
76
+ and enforce host-side egress plus guest account policy before advertising those
77
+ capabilities. A shell for a network-isolated guest needs staged artifacts and an
78
+ explicit control channel; enabling arbitrary networking for bootstrap is unsafe.
@@ -0,0 +1,70 @@
1
+ # MCP Shipyard
2
+
3
+ Install the `mcp-shipyard` distribution and import `mcp_shipyard`:
4
+
5
+ ```sh
6
+ python -m pip install mcp-shipyard
7
+ shipyard --help
8
+ ```
9
+
10
+ `mcp_shipyard` manages MCP applications independently of their tool set.
11
+ It contains no Exactus, District, Conjure, FastAPI, or Docker imports.
12
+
13
+ - `models.py`: JSON types, server identity, protocol failures, and outcomes.
14
+ - `payloads.py`: request envelope decoding and validation.
15
+ - `responses.py`: protocol and tool result encoding.
16
+ - `server.py`: negotiation, discovery, authenticated dispatch, and observers.
17
+ - `tools.py`: tool registration and explicit application authorization.
18
+ - `pipelines.py`: pipeline request and executor contracts, and tool adapter.
19
+ - `stdio.py`: bounded newline-delimited transport with caller-owned streams.
20
+ - `environments.py`: approved profiles, enforceable permissions, and disposable
21
+ worker lifecycle shared by providers.
22
+ - `lease_store.py` and `leases.py`: durable ownership, expiration, serialization,
23
+ restart recovery contracts and confirmed worker cleanup.
24
+ - `cli.py` and `__main__.py`: explicit application factory and local principal
25
+ for `python -m mcp_shipyard serve`.
26
+ - `__init__.py`: public API.
27
+ - `features.py`: optional terminal, package-install and resource-relay contracts.
28
+ - `pyproject.toml`: the independently installable `mcp-shipyard` distribution.
29
+
30
+ Applications supply identity, schemas, handlers, and authorization. Monitoring
31
+ is an injected observer; District can consume those events without becoming
32
+ the MCP application's protocol core. Pipeline adapters own worker selection,
33
+ queues, leases, cancellation, and cleanup. The host never interprets arbitrary
34
+ payload values as executable code. Unexpected application errors propagate;
35
+ only explicitly configured safe error types become MCP tool-error content.
36
+
37
+ The Exactus adapter in `mcp/exactus_mcp/mcp_server.py` now uses this shared
38
+ host and registry while retaining its existing tool names and permissions.
39
+ Its environment and REST services remain application adapters; importing this
40
+ library never imports or starts them. Observer parameters are transient and
41
+ must be redacted by a monitor before persistence. Stdio adapters do not close
42
+ streams owned by their callers.
43
+
44
+ Run the independent example from the repository root:
45
+
46
+ ```sh
47
+ python -m mcp_shipyard serve \
48
+ --application examples.tools.mcp_shipyard.echo:create --principal example-user
49
+ ```
50
+
51
+ The application factory and stdio principal are trusted operator configuration.
52
+ They must never be populated directly from remote request arguments.
53
+
54
+ Environment callers choose a registered profile and an argv vector. They
55
+ cannot override that profile's network, privilege, package, or exposure policy.
56
+ Providers must enforce both grants and denials before starting a worker.
57
+ The Conjure Docker adapter currently enforces Linux containers, non-root
58
+ execution, dropped capabilities, no privilege escalation, bounded CPU/memory,
59
+ read-only root, private scratch, bounded output and destruction on every exit.
60
+ Network `none` disables guest networking; `internet` enables Docker bridge
61
+ networking, including reachable private destinations. It is not an Internet-only
62
+ firewall. Allow-list egress, privilege grants, managed package installs, resource
63
+ publishing and interactive shell installation are refused by this adapter.
64
+
65
+ VM providers use the same interface, but no audited Windows or macOS template
66
+ is shipped here. Contract tests for those platform names are not native guest
67
+ execution evidence. A future VM adapter must clone immutable prepared images
68
+ and enforce host-side egress plus guest account policy before advertising those
69
+ capabilities. A shell for a network-isolated guest needs staged artifacts and an
70
+ explicit control channel; enabling arbitrary networking for bootstrap is unsafe.
@@ -0,0 +1,31 @@
1
+ """Application-independent Model Context Protocol server infrastructure."""
2
+
3
+ from .environments import (
4
+ EnvironmentPipeline,
5
+ EnvironmentProfile,
6
+ ResourcePolicy,
7
+ )
8
+ from .features import ExtendedEnvironment, TerminalChannel
9
+ from .lease_store import Lease, LeaseStore
10
+ from .leases import EnvironmentLeases, ManagedProvider
11
+ from .models import JsonObject, JsonValue, Outcome, RpcError, ServerIdentity
12
+ from .server import McpServer, RequestEvent
13
+
14
+ __all__: tuple[str, ...] = (
15
+ "EnvironmentLeases",
16
+ "EnvironmentPipeline",
17
+ "EnvironmentProfile",
18
+ "ExtendedEnvironment",
19
+ "JsonObject",
20
+ "JsonValue",
21
+ "Lease",
22
+ "LeaseStore",
23
+ "ManagedProvider",
24
+ "McpServer",
25
+ "Outcome",
26
+ "RequestEvent",
27
+ "ResourcePolicy",
28
+ "RpcError",
29
+ "ServerIdentity",
30
+ "TerminalChannel",
31
+ )
@@ -0,0 +1,6 @@
1
+ """Launch a configured MCP application with ``python -m mcp_shipyard``."""
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
@@ -0,0 +1,85 @@
1
+ """Launch an operator-selected MCP application over the shared stdio host."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from argparse import ArgumentParser, Namespace
6
+ from importlib import import_module
7
+ from re import fullmatch
8
+ from sys import stdin, stdout
9
+ from typing import TYPE_CHECKING, Protocol, cast
10
+
11
+ from .stdio import serve
12
+
13
+ if TYPE_CHECKING:
14
+ from types import ModuleType
15
+
16
+ from .server import McpServer
17
+
18
+
19
+ class ApplicationFactory(Protocol):
20
+ """Construct an application from trusted launcher configuration."""
21
+
22
+ def __call__(self) -> McpServer[str]:
23
+ """Create a fully configured MCP application.
24
+
25
+ Returns:
26
+ Host, registered tools, authorization, and optional observers.
27
+
28
+ """
29
+ ...
30
+
31
+
32
+ def main(arguments: list[str] | None = None) -> int:
33
+ """Launch a configured application without embedding its tool set.
34
+
35
+ The module is operator configuration, never an agent payload. Stdio
36
+ authority belongs to the launching parent process; HTTP authentication
37
+ must be supplied by that transport's adapter.
38
+
39
+ Args:
40
+ arguments: Optional command-line argument list.
41
+
42
+ Returns:
43
+ Zero after clean stdin EOF.
44
+
45
+ """
46
+ parser: ArgumentParser = ArgumentParser(description=__doc__)
47
+ launcher: ArgumentParser = parser.add_subparsers(
48
+ dest="command",
49
+ required=True,
50
+ ).add_parser(name="serve")
51
+ launcher.add_argument(
52
+ "--application",
53
+ required=True,
54
+ metavar="MODULE:FACTORY",
55
+ help="Trusted local application factory; never accepted from MCP.",
56
+ )
57
+ launcher.add_argument(
58
+ "--principal",
59
+ required=True,
60
+ help="Parent-established identity interpreted by the application.",
61
+ )
62
+ parsed: Namespace = parser.parse_args(args=arguments)
63
+ target: str = parsed.application
64
+ if (
65
+ fullmatch(
66
+ pattern=r"[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*:[A-Za-z_]\w*",
67
+ string=target,
68
+ )
69
+ is None
70
+ ):
71
+ parser.error(message="--application must be a local MODULE:FACTORY")
72
+ module_name: str
73
+ factory_name: str
74
+ module_name, _, factory_name = target.partition(":")
75
+ module: ModuleType = import_module(name=module_name)
76
+ factory: ApplicationFactory = cast(
77
+ typ="ApplicationFactory",
78
+ val=getattr(module, factory_name),
79
+ )
80
+ return serve(
81
+ server=factory(),
82
+ principal=parsed.principal,
83
+ reader=stdin.buffer,
84
+ writer=stdout.buffer,
85
+ )
@@ -0,0 +1,327 @@
1
+ """Define one fail-closed disposable-environment workflow for every backend.
2
+
3
+ Profiles and providers are registered by the operator, never constructed from
4
+ agent-supplied capability claims. A provider must implement every requested
5
+ grant and denial. Merely running a command on a host is not sandboxing.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from math import isfinite
12
+ from secrets import token_hex
13
+ from typing import TYPE_CHECKING, Final, Literal, Protocol, final
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Mapping
17
+
18
+ from .models import JsonObject, JsonValue
19
+ from .pipelines import PipelineRequest
20
+
21
+ type NetworkMode = Literal["none", "internet", "allowlist"]
22
+ type PackageMode = Literal["disabled", "environment", "system"]
23
+
24
+
25
+ @final
26
+ class Constants:
27
+ """Bound agent-controlled command vectors.
28
+
29
+ Attributes:
30
+ max_arguments: Maximum command argument count.
31
+
32
+ """
33
+
34
+ max_arguments: Final[int] = 256
35
+
36
+
37
+ class EnvironmentPolicyError(ValueError):
38
+ """Reject an unenforceable policy before starting any worker."""
39
+
40
+
41
+ @dataclass(frozen=True, slots=True, kw_only=True)
42
+ class ResourcePolicy:
43
+ """Express grants and denials independently of host and guest OS.
44
+
45
+ Package policy applies to the broker's managed installation operation.
46
+ It does not claim that arbitrary writable code cannot import local files.
47
+
48
+ Attributes:
49
+ network: Egress mode enforced outside agent control.
50
+ allowed_hosts: Egress destinations when an allow-list is selected.
51
+ elevation: Whether guest elevation may be offered, never host sudo.
52
+ packages: Managed package installation scope.
53
+ expose_resources: Whether authenticated resources may be published.
54
+ timeout_seconds: Maximum pipeline command lifetime.
55
+
56
+ """
57
+
58
+ network: NetworkMode = "none"
59
+ allowed_hosts: tuple[str, ...] = ()
60
+ elevation: bool = False
61
+ packages: PackageMode = "disabled"
62
+ expose_resources: bool = False
63
+ timeout_seconds: float = 300.0
64
+
65
+ def __post_init__(self) -> None:
66
+ """Reject ambiguous and unbounded security requests.
67
+
68
+ Raises:
69
+ EnvironmentPolicyError: When policy dimensions are inconsistent.
70
+
71
+ """
72
+ if (
73
+ type(self.elevation) is not bool
74
+ or type(self.expose_resources) is not bool
75
+ or isinstance(self.timeout_seconds, bool)
76
+ or self.network not in ("none", "internet", "allowlist")
77
+ or self.packages not in ("disabled", "environment", "system")
78
+ or not isfinite(self.timeout_seconds)
79
+ or self.timeout_seconds <= 0
80
+ or bool(self.allowed_hosts) != (self.network == "allowlist")
81
+ or any(not host or "\x00" in host for host in self.allowed_hosts)
82
+ ):
83
+ msg: str = "Invalid disposable environment policy"
84
+ raise EnvironmentPolicyError(msg)
85
+
86
+
87
+ @dataclass(frozen=True, slots=True, kw_only=True)
88
+ class RuntimeCapabilities:
89
+ """Declare policy combinations a trusted provider can enforce.
90
+
91
+ These declarations belong to audited provider implementations, not to
92
+ clients or unverified image metadata. Denying a capability is itself a
93
+ capability: a VM with default administrator credentials cannot claim
94
+ ``elevation_modes={False}``.
95
+
96
+ Attributes:
97
+ isolated: Whether this is a real disposable isolation boundary.
98
+ platforms: Supported guest OS identifiers.
99
+ network_modes: Egress modes this provider enforces.
100
+ elevation_modes: Guest privilege modes this provider enforces.
101
+ package_modes: Managed package installation modes.
102
+ exposure_modes: Whether resources may be published.
103
+ supports_district_shell: Whether a scoped shell can be installed.
104
+
105
+ """
106
+
107
+ isolated: bool
108
+ platforms: frozenset[str]
109
+ network_modes: frozenset[NetworkMode]
110
+ elevation_modes: frozenset[bool]
111
+ package_modes: frozenset[PackageMode]
112
+ exposure_modes: frozenset[bool]
113
+ supports_district_shell: bool = False
114
+
115
+ def require(self, *, profile: EnvironmentProfile) -> None:
116
+ """Refuse unsupported grants and denials before worker creation.
117
+
118
+ Args:
119
+ profile: Operator-approved environment configuration.
120
+
121
+ Raises:
122
+ EnvironmentPolicyError: When any requested dimension is missing.
123
+
124
+ """
125
+ policy: ResourcePolicy = profile.policy
126
+ if not (
127
+ self.isolated
128
+ and profile.platform in self.platforms
129
+ and policy.network in self.network_modes
130
+ and policy.elevation in self.elevation_modes
131
+ and policy.packages in self.package_modes
132
+ and policy.expose_resources in self.exposure_modes
133
+ and (not profile.district_shell or self.supports_district_shell)
134
+ ):
135
+ msg: str = "Provider cannot enforce the complete requested policy"
136
+ raise EnvironmentPolicyError(msg)
137
+
138
+
139
+ @dataclass(frozen=True, slots=True, kw_only=True)
140
+ class EnvironmentProfile:
141
+ """Select one operator-approved template and security policy.
142
+
143
+ Attributes:
144
+ provider: Registered driver identifier.
145
+ template: Immutable prepared image or snapshot identifier.
146
+ platform: Guest OS identifier understood by the driver.
147
+ policy: Explicit resource policy.
148
+ district_shell: Whether to install an environment-scoped shell.
149
+
150
+ """
151
+
152
+ provider: str
153
+ template: str
154
+ platform: str
155
+ policy: ResourcePolicy = ResourcePolicy()
156
+ district_shell: bool = False
157
+
158
+
159
+ @dataclass(frozen=True, slots=True, kw_only=True)
160
+ class EnvironmentCommandResult:
161
+ """Return a command result without prescribing a transport.
162
+
163
+ Attributes:
164
+ exit_code: Actual guest command exit status.
165
+ stdout: Bounded command output.
166
+ stderr: Bounded diagnostic output.
167
+
168
+ """
169
+
170
+ exit_code: int
171
+ stdout: str
172
+ stderr: str
173
+
174
+
175
+ class DisposableEnvironment(Protocol):
176
+ """Own one disposable worker and its full cleanup lifecycle."""
177
+
178
+ def run(
179
+ self,
180
+ *,
181
+ argv: tuple[str, ...],
182
+ timeout_seconds: float,
183
+ ) -> EnvironmentCommandResult:
184
+ """Execute argv without implicit shell interpolation.
185
+
186
+ Args:
187
+ argv: Guest command vector.
188
+ timeout_seconds: Command deadline enforced by the provider.
189
+
190
+ Returns:
191
+ Real command status and bounded output.
192
+
193
+ """
194
+ ...
195
+
196
+ def install_shell(self) -> None:
197
+ """Install the approved shell using scoped bootstrap credentials."""
198
+ ...
199
+
200
+ def close(self) -> None:
201
+ """Destroy only this disposable worker and its owned scratch data.
202
+
203
+ Providers must make this idempotent and preserve immutable templates.
204
+ """
205
+ ...
206
+
207
+
208
+ class EnvironmentProvider(Protocol):
209
+ """Provide OS-specific isolation behind the same lifecycle contract."""
210
+
211
+ @property
212
+ def capabilities(self) -> RuntimeCapabilities:
213
+ """Describe policy combinations this driver actually enforces.
214
+
215
+ Returns:
216
+ Trusted runtime capabilities.
217
+
218
+ """
219
+ ...
220
+
221
+ def create(
222
+ self,
223
+ *,
224
+ name: str,
225
+ profile: EnvironmentProfile,
226
+ ) -> DisposableEnvironment:
227
+ """Create a disposable clone, cleaning up partial failures itself.
228
+
229
+ Args:
230
+ name: Unique name generated by the host, never supplied by agents.
231
+ profile: Validated operator-approved template and policy.
232
+
233
+ Returns:
234
+ Started worker whose lifecycle belongs to the caller.
235
+
236
+ """
237
+ ...
238
+
239
+
240
+ class EnvironmentPipeline[Identity]:
241
+ """Use one workflow for Docker and every registered VM guest OS."""
242
+
243
+ def __init__(
244
+ self,
245
+ *,
246
+ profiles: Mapping[str, EnvironmentProfile],
247
+ providers: Mapping[str, EnvironmentProvider],
248
+ ) -> None:
249
+ """Freeze the operator's approved environment catalog.
250
+
251
+ Args:
252
+ profiles: Agent-selectable names mapped to approved policies.
253
+ providers: Trusted implementations, such as Conjure adapters.
254
+
255
+ """
256
+ self._profiles: dict[str, EnvironmentProfile] = dict(profiles)
257
+ self._providers: dict[str, EnvironmentProvider] = dict(providers)
258
+
259
+ def execute(self, *, request: PipelineRequest[Identity]) -> JsonObject:
260
+ """Validate, clone, optionally install a shell, execute, and destroy.
261
+
262
+ Args:
263
+ request: Authorized tool payload with ``profile`` and ``argv``.
264
+ Client-supplied policy overrides are rejected.
265
+
266
+ Returns:
267
+ Command outcome after required cleanup has completed.
268
+
269
+ Raises:
270
+ EnvironmentPolicyError: When the request or policy is unsafe.
271
+ BaseExceptionGroup: When both execution and cleanup fail.
272
+
273
+ """
274
+ name: JsonValue = request.payload.get("profile")
275
+ raw: JsonValue = request.payload.get("argv")
276
+ if (
277
+ not isinstance(name, str)
278
+ or name not in self._profiles
279
+ or not isinstance(raw, list)
280
+ or not raw
281
+ or len(raw) > Constants.max_arguments
282
+ or any(not isinstance(arg, str) or "\x00" in arg for arg in raw)
283
+ or set(request.payload) - {"profile", "argv"}
284
+ ):
285
+ msg: str = "Expected an approved profile and a safe argv vector"
286
+ raise EnvironmentPolicyError(msg)
287
+ argv: tuple[str, ...] = tuple(
288
+ arg for arg in raw if isinstance(arg, str)
289
+ )
290
+ profile: EnvironmentProfile = self._profiles[name]
291
+ provider: EnvironmentProvider | None = self._providers.get(
292
+ profile.provider,
293
+ )
294
+ if provider is None:
295
+ msg = "Environment provider is not registered"
296
+ raise EnvironmentPolicyError(msg)
297
+ provider.capabilities.require(profile=profile)
298
+ environment: DisposableEnvironment = provider.create(
299
+ name=f"mcp-{token_hex(nbytes=12)}",
300
+ profile=profile,
301
+ )
302
+ result: EnvironmentCommandResult
303
+ try:
304
+ if profile.district_shell:
305
+ environment.install_shell()
306
+ result = environment.run(
307
+ argv=argv,
308
+ timeout_seconds=profile.policy.timeout_seconds,
309
+ )
310
+ except BaseException as primary:
311
+ try:
312
+ environment.close()
313
+ except (OSError, RuntimeError, ValueError) as cleanup:
314
+ msg = "Environment execution and cleanup failed"
315
+ raise BaseExceptionGroup(
316
+ msg,
317
+ [primary, cleanup],
318
+ ) from None
319
+ raise
320
+ environment.close()
321
+ return {
322
+ "exit_code": result.exit_code,
323
+ "stdout": result.stdout,
324
+ "stderr": result.stderr,
325
+ "provider": profile.provider,
326
+ "platform": profile.platform,
327
+ }
@@ -0,0 +1,98 @@
1
+ """Describe optional guest operations without depending on a VM backend."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
6
+
7
+ from .environments import DisposableEnvironment
8
+
9
+ if TYPE_CHECKING:
10
+ from pathlib import Path
11
+
12
+ from .environments import EnvironmentCommandResult
13
+ from .models import JsonObject
14
+
15
+
16
+ class TerminalChannel(Protocol):
17
+ """Own a bounded terminal stream until the environment is destroyed."""
18
+
19
+ def read(self, *, maximum: int = 65536) -> bytes:
20
+ """Read one bounded chunk, returning empty bytes at EOF.
21
+
22
+ Args:
23
+ maximum: Maximum bytes returned by one read.
24
+
25
+ Returns:
26
+ Terminal output bytes.
27
+
28
+ """
29
+ ...
30
+
31
+ def write(self, *, data: bytes) -> None:
32
+ """Write bounded terminal input without interpreting it on the host.
33
+
34
+ Args:
35
+ data: Raw input for the guest terminal.
36
+
37
+ """
38
+ ...
39
+
40
+ def close(self) -> None:
41
+ """Stop and reap the owned control process."""
42
+ ...
43
+
44
+
45
+ @runtime_checkable
46
+ class ExtendedEnvironment(DisposableEnvironment, Protocol):
47
+ """Provide optional operations within the same isolation boundary."""
48
+
49
+ def open_terminal(self) -> TerminalChannel:
50
+ """Open a guest terminal without enabling guest Internet access.
51
+
52
+ Returns:
53
+ Owned byte stream; closing its lease destroys all guest children.
54
+
55
+ """
56
+ ...
57
+
58
+ def install_packages(
59
+ self,
60
+ *,
61
+ wheels: tuple[Path, ...],
62
+ system: bool,
63
+ ) -> EnvironmentCommandResult:
64
+ """Install only staged, approved wheel artifacts without index access.
65
+
66
+ Args:
67
+ wheels: Verified local wheel copies selected by the broker.
68
+ system: Whether an explicit guest system-package grant applies.
69
+
70
+ Returns:
71
+ Actual offline pip outcome.
72
+
73
+ """
74
+ ...
75
+
76
+ def request_resource(
77
+ self,
78
+ *,
79
+ port: int,
80
+ method: str,
81
+ path: str,
82
+ body: bytes,
83
+ content_type: str,
84
+ ) -> JsonObject:
85
+ """Relay bounded HTTP to guest loopback without opening a host port.
86
+
87
+ Args:
88
+ port: Operator-approved guest service port.
89
+ method: Allowed HTTP method.
90
+ path: Relative HTTP request target.
91
+ body: Bounded request body.
92
+ content_type: Validated request media type.
93
+
94
+ Returns:
95
+ Bounded response status, headers and base64 content.
96
+
97
+ """
98
+ ...