Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,26 @@ For complete runnable Go programs:
# Delete the ate-env namespace to remove all components:
kubectl delete ns ate-env
kubectl ate delete actor-template --atespace ate-env default-template
```
```
## Guest ownership for snapshot restores

`ate-env-guest --require-owner --workspace=/workspace` opts into Linux
controller fencing. Each gRPC call must carry `ate-owner-generation` (a
positive, monotonically increasing int64 issued outside the guest) and
`ate-owner-token`. A higher generation cancels and finishes the prior owner's
RPCs, drains tracked process groups, and admits the new owner. Lower
generations and a different token for the current generation are rejected.
This is fencing, not authentication: use an authenticated router and a trusted
controller; do not expose the guest directly to untrusted callers.

`StartProcessRequest.process_id` accepts a caller-generated UUID. Killing an
unknown UUID records cancellation before a delayed start arrives. Duplicate
starts fail rather than replay a command. `KillAllProcesses` drains tracked
process groups before suspension. Output RPCs transfer at most 64 KiB per
message and drain the final output after the process reaper finishes.

File reads, writes, and `RemovePath` use Go's rooted filesystem operations,
including symlink and rename protection. Ownership mode also holds a rooted
working-directory descriptor through Linux process startup. Processes are
still arbitrary programs inside the actor sandbox; filesystem RPC confinement
is not a substitute for the sandbox itself.
6 changes: 6 additions & 0 deletions clients/python/scripts/gen-protos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ for f in "${OUT_DIR}"/ateenv/v1alpha/*_pb2_grpc.py; do
sed -i.bak 's/^from ateenv\.v1alpha import \(.*\)$/from . import \1/' "${f}"
rm -f "${f}.bak"
done

# Keep generated files subject to the repository's normal license gate.
for f in "${OUT_DIR}"/ateenv/v1alpha/*_pb2.py "${OUT_DIR}"/ateenv/v1alpha/*_pb2.pyi "${OUT_DIR}"/ateenv/v1alpha/*_pb2_grpc.py; do
{ cat hack/boilerplate/sh.txt; cat "$f"; } > "${f}.tmp"
mv "${f}.tmp" "$f"
done
91 changes: 52 additions & 39 deletions clients/python/src/ate_env/_gen/ateenv/v1alpha/guest_pb2.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,63 +14,76 @@

# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: ateenv/v1alpha/guest.proto
# Protobuf Python Version: 4.25.1
# Protobuf Python Version: 7.35.1
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
7,
35,
1,
'',
'ateenv/v1alpha/guest.proto'
)
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()


from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2


DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x61teenv/v1alpha/guest.proto\x12\x0e\x61teenv.v1alpha\x1a\x1fgoogle/protobuf/timestamp.proto\"\xc0\x01\n\x07Process\x12\x12\n\nprocess_id\x18\x01 \x01(\t\x12-\n\x06status\x18\x02 \x01(\x0e\x32\x1d.ateenv.v1alpha.ProcessStatus\x12\x11\n\texit_code\x18\x03 \x01(\x05\x12.\n\nstarted_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0b\x66inished_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x9a\x01\n\x13StartProcessRequest\x12\x0f\n\x07\x63ommand\x18\x01 \x03(\t\x12\x0b\n\x03\x63wd\x18\x02 \x01(\t\x12\x39\n\x03\x65nv\x18\x03 \x03(\x0b\x32,.ateenv.v1alpha.StartProcessRequest.EnvEntry\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"*\n\x14StartProcessResponse\x12\x12\n\nprocess_id\x18\x01 \x01(\t\"\'\n\x11GetProcessRequest\x12\x12\n\nprocess_id\x18\x01 \x01(\t\"o\n\x1bStreamProcessOutputsRequest\x12\x12\n\nprocess_id\x18\x01 \x01(\t\x12\x15\n\rstdout_offset\x18\x02 \x01(\x03\x12\x15\n\rstderr_offset\x18\x03 \x01(\x03\x12\x0e\n\x06\x66ollow\x18\x04 \x01(\x08\"I\n\x0bOutputChunk\x12,\n\x06source\x18\x01 \x01(\x0e\x32\x1c.ateenv.v1alpha.OutputSource\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"(\n\x12KillProcessRequest\x12\x12\n\nprocess_id\x18\x01 \x01(\t\"(\n\x13KillProcessResponse\x12\x11\n\texit_code\x18\x01 \x01(\x05\"\x1f\n\x0fReadFileRequest\x12\x0c\n\x04path\x18\x01 \x01(\t\"\x19\n\tFileChunk\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"=\n\x10WriteFileRequest\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\r\n\x05\x63hunk\x18\x02 \x01(\x0c\x12\x0c\n\x04mode\x18\x03 \x01(\r\"*\n\x11WriteFileResponse\x12\x15\n\rbytes_written\x18\x01 \x01(\x03*\xa3\x01\n\rProcessStatus\x12\x1e\n\x1aPROCESS_STATUS_UNSPECIFIED\x10\x00\x12\x1a\n\x16PROCESS_STATUS_RUNNING\x10\x01\x12\x1c\n\x18PROCESS_STATUS_COMPLETED\x10\x02\x12\x19\n\x15PROCESS_STATUS_FAILED\x10\x03\x12\x1d\n\x19PROCESS_STATUS_TERMINATED\x10\x04*a\n\x0cOutputSource\x12\x1d\n\x19OUTPUT_SOURCE_UNSPECIFIED\x10\x00\x12\x18\n\x14OUTPUT_SOURCE_STDOUT\x10\x01\x12\x18\n\x14OUTPUT_SOURCE_STDERR\x10\x02\x32\xf1\x02\n\x0eProcessService\x12Y\n\x0cStartProcess\x12#.ateenv.v1alpha.StartProcessRequest\x1a$.ateenv.v1alpha.StartProcessResponse\x12H\n\nGetProcess\x12!.ateenv.v1alpha.GetProcessRequest\x1a\x17.ateenv.v1alpha.Process\x12\x62\n\x14StreamProcessOutputs\x12+.ateenv.v1alpha.StreamProcessOutputsRequest\x1a\x1b.ateenv.v1alpha.OutputChunk0\x01\x12V\n\x0bKillProcess\x12\".ateenv.v1alpha.KillProcessRequest\x1a#.ateenv.v1alpha.KillProcessResponse2\xb1\x01\n\x11\x46ileSystemService\x12H\n\x08ReadFile\x12\x1f.ateenv.v1alpha.ReadFileRequest\x1a\x19.ateenv.v1alpha.FileChunk0\x01\x12R\n\tWriteFile\x12 .ateenv.v1alpha.WriteFileRequest\x1a!.ateenv.v1alpha.WriteFileResponse(\x01\x42\x43ZAgithub.com/agent-substrate/env/proto/ateenv/v1alpha;ateenvv1alphab\x06proto3')
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1a\x61teenv/v1alpha/guest.proto\x12\x0e\x61teenv.v1alpha\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1bgoogle/protobuf/empty.proto\"\xc0\x01\n\x07Process\x12\x12\n\nprocess_id\x18\x01 \x01(\t\x12-\n\x06status\x18\x02 \x01(\x0e\x32\x1d.ateenv.v1alpha.ProcessStatus\x12\x11\n\texit_code\x18\x03 \x01(\x05\x12.\n\nstarted_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0b\x66inished_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xae\x01\n\x13StartProcessRequest\x12\x0f\n\x07\x63ommand\x18\x01 \x03(\t\x12\x0b\n\x03\x63wd\x18\x02 \x01(\t\x12\x39\n\x03\x65nv\x18\x03 \x03(\x0b\x32,.ateenv.v1alpha.StartProcessRequest.EnvEntry\x12\x12\n\nprocess_id\x18\x04 \x01(\t\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"*\n\x14StartProcessResponse\x12\x12\n\nprocess_id\x18\x01 \x01(\t\"\'\n\x11GetProcessRequest\x12\x12\n\nprocess_id\x18\x01 \x01(\t\"o\n\x1bStreamProcessOutputsRequest\x12\x12\n\nprocess_id\x18\x01 \x01(\t\x12\x15\n\rstdout_offset\x18\x02 \x01(\x03\x12\x15\n\rstderr_offset\x18\x03 \x01(\x03\x12\x0e\n\x06\x66ollow\x18\x04 \x01(\x08\"I\n\x0bOutputChunk\x12,\n\x06source\x18\x01 \x01(\x0e\x32\x1c.ateenv.v1alpha.OutputSource\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"(\n\x12KillProcessRequest\x12\x12\n\nprocess_id\x18\x01 \x01(\t\"(\n\x13KillProcessResponse\x12\x11\n\texit_code\x18\x01 \x01(\x05\"\x1f\n\x0fReadFileRequest\x12\x0c\n\x04path\x18\x01 \x01(\t\"\x19\n\tFileChunk\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"=\n\x10WriteFileRequest\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\r\n\x05\x63hunk\x18\x02 \x01(\x0c\x12\x0c\n\x04mode\x18\x03 \x01(\r\"*\n\x11WriteFileResponse\x12\x15\n\rbytes_written\x18\x01 \x01(\x03\"C\n\x11RemovePathRequest\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x11\n\trecursive\x18\x02 \x01(\x08\x12\r\n\x05\x66orce\x18\x03 \x01(\x08*\xa3\x01\n\rProcessStatus\x12\x1e\n\x1aPROCESS_STATUS_UNSPECIFIED\x10\x00\x12\x1a\n\x16PROCESS_STATUS_RUNNING\x10\x01\x12\x1c\n\x18PROCESS_STATUS_COMPLETED\x10\x02\x12\x19\n\x15PROCESS_STATUS_FAILED\x10\x03\x12\x1d\n\x19PROCESS_STATUS_TERMINATED\x10\x04*a\n\x0cOutputSource\x12\x1d\n\x19OUTPUT_SOURCE_UNSPECIFIED\x10\x00\x12\x18\n\x14OUTPUT_SOURCE_STDOUT\x10\x01\x12\x18\n\x14OUTPUT_SOURCE_STDERR\x10\x02\x32\xb5\x03\n\x0eProcessService\x12Y\n\x0cStartProcess\x12#.ateenv.v1alpha.StartProcessRequest\x1a$.ateenv.v1alpha.StartProcessResponse\x12H\n\nGetProcess\x12!.ateenv.v1alpha.GetProcessRequest\x1a\x17.ateenv.v1alpha.Process\x12\x62\n\x14StreamProcessOutputs\x12+.ateenv.v1alpha.StreamProcessOutputsRequest\x1a\x1b.ateenv.v1alpha.OutputChunk0\x01\x12V\n\x0bKillProcess\x12\".ateenv.v1alpha.KillProcessRequest\x1a#.ateenv.v1alpha.KillProcessResponse\x12\x42\n\x10KillAllProcesses\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty2\xfa\x01\n\x11\x46ileSystemService\x12H\n\x08ReadFile\x12\x1f.ateenv.v1alpha.ReadFileRequest\x1a\x19.ateenv.v1alpha.FileChunk0\x01\x12R\n\tWriteFile\x12 .ateenv.v1alpha.WriteFileRequest\x1a!.ateenv.v1alpha.WriteFileResponse(\x01\x12G\n\nRemovePath\x12!.ateenv.v1alpha.RemovePathRequest\x1a\x16.google.protobuf.EmptyBCZAgithub.com/agent-substrate/env/proto/ateenv/v1alpha;ateenvv1alphab\x06proto3')

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ateenv.v1alpha.guest_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
_globals['DESCRIPTOR']._options = None
if not _descriptor._USE_C_DESCRIPTORS:
_globals['DESCRIPTOR']._loaded_options = None
_globals['DESCRIPTOR']._serialized_options = b'ZAgithub.com/agent-substrate/env/proto/ateenv/v1alpha;ateenvv1alpha'
_globals['_STARTPROCESSREQUEST_ENVENTRY']._options = None
_globals['_STARTPROCESSREQUEST_ENVENTRY']._loaded_options = None
_globals['_STARTPROCESSREQUEST_ENVENTRY']._serialized_options = b'8\001'
_globals['_PROCESSSTATUS']._serialized_start=956
_globals['_PROCESSSTATUS']._serialized_end=1119
_globals['_OUTPUTSOURCE']._serialized_start=1121
_globals['_OUTPUTSOURCE']._serialized_end=1218
_globals['_PROCESS']._serialized_start=80
_globals['_PROCESS']._serialized_end=272
_globals['_STARTPROCESSREQUEST']._serialized_start=275
_globals['_STARTPROCESSREQUEST']._serialized_end=429
_globals['_STARTPROCESSREQUEST_ENVENTRY']._serialized_start=387
_globals['_STARTPROCESSREQUEST_ENVENTRY']._serialized_end=429
_globals['_STARTPROCESSRESPONSE']._serialized_start=431
_globals['_STARTPROCESSRESPONSE']._serialized_end=473
_globals['_GETPROCESSREQUEST']._serialized_start=475
_globals['_GETPROCESSREQUEST']._serialized_end=514
_globals['_STREAMPROCESSOUTPUTSREQUEST']._serialized_start=516
_globals['_STREAMPROCESSOUTPUTSREQUEST']._serialized_end=627
_globals['_OUTPUTCHUNK']._serialized_start=629
_globals['_OUTPUTCHUNK']._serialized_end=702
_globals['_KILLPROCESSREQUEST']._serialized_start=704
_globals['_KILLPROCESSREQUEST']._serialized_end=744
_globals['_KILLPROCESSRESPONSE']._serialized_start=746
_globals['_KILLPROCESSRESPONSE']._serialized_end=786
_globals['_READFILEREQUEST']._serialized_start=788
_globals['_READFILEREQUEST']._serialized_end=819
_globals['_FILECHUNK']._serialized_start=821
_globals['_FILECHUNK']._serialized_end=846
_globals['_WRITEFILEREQUEST']._serialized_start=848
_globals['_WRITEFILEREQUEST']._serialized_end=909
_globals['_WRITEFILERESPONSE']._serialized_start=911
_globals['_WRITEFILERESPONSE']._serialized_end=953
_globals['_PROCESSSERVICE']._serialized_start=1221
_globals['_PROCESSSERVICE']._serialized_end=1590
_globals['_FILESYSTEMSERVICE']._serialized_start=1593
_globals['_FILESYSTEMSERVICE']._serialized_end=1770
_globals['_PROCESSSTATUS']._serialized_start=1074
_globals['_PROCESSSTATUS']._serialized_end=1237
_globals['_OUTPUTSOURCE']._serialized_start=1239
_globals['_OUTPUTSOURCE']._serialized_end=1336
_globals['_PROCESS']._serialized_start=109
_globals['_PROCESS']._serialized_end=301
_globals['_STARTPROCESSREQUEST']._serialized_start=304
_globals['_STARTPROCESSREQUEST']._serialized_end=478
_globals['_STARTPROCESSREQUEST_ENVENTRY']._serialized_start=436
_globals['_STARTPROCESSREQUEST_ENVENTRY']._serialized_end=478
_globals['_STARTPROCESSRESPONSE']._serialized_start=480
_globals['_STARTPROCESSRESPONSE']._serialized_end=522
_globals['_GETPROCESSREQUEST']._serialized_start=524
_globals['_GETPROCESSREQUEST']._serialized_end=563
_globals['_STREAMPROCESSOUTPUTSREQUEST']._serialized_start=565
_globals['_STREAMPROCESSOUTPUTSREQUEST']._serialized_end=676
_globals['_OUTPUTCHUNK']._serialized_start=678
_globals['_OUTPUTCHUNK']._serialized_end=751
_globals['_KILLPROCESSREQUEST']._serialized_start=753
_globals['_KILLPROCESSREQUEST']._serialized_end=793
_globals['_KILLPROCESSRESPONSE']._serialized_start=795
_globals['_KILLPROCESSRESPONSE']._serialized_end=835
_globals['_READFILEREQUEST']._serialized_start=837
_globals['_READFILEREQUEST']._serialized_end=868
_globals['_FILECHUNK']._serialized_start=870
_globals['_FILECHUNK']._serialized_end=895
_globals['_WRITEFILEREQUEST']._serialized_start=897
_globals['_WRITEFILEREQUEST']._serialized_end=958
_globals['_WRITEFILERESPONSE']._serialized_start=960
_globals['_WRITEFILERESPONSE']._serialized_end=1002
_globals['_REMOVEPATHREQUEST']._serialized_start=1004
_globals['_REMOVEPATHREQUEST']._serialized_end=1071
_globals['_PROCESSSERVICE']._serialized_start=1339
_globals['_PROCESSSERVICE']._serialized_end=1776
_globals['_FILESYSTEMSERVICE']._serialized_start=1779
_globals['_FILESYSTEMSERVICE']._serialized_end=2029
# @@protoc_insertion_point(module_scope)
52 changes: 34 additions & 18 deletions clients/python/src/ate_env/_gen/ateenv/v1alpha/guest_pb2.pyi
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import datetime

from google.protobuf import timestamp_pb2 as _timestamp_pb2
from google.protobuf import empty_pb2 as _empty_pb2
from google.protobuf.internal import containers as _containers
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union

DESCRIPTOR: _descriptor.FileDescriptor

Expand Down Expand Up @@ -55,10 +59,10 @@ class Process(_message.Message):
exit_code: int
started_at: _timestamp_pb2.Timestamp
finished_at: _timestamp_pb2.Timestamp
def __init__(self, process_id: _Optional[str] = ..., status: _Optional[_Union[ProcessStatus, str]] = ..., exit_code: _Optional[int] = ..., started_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., finished_at: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...
def __init__(self, process_id: _Optional[str] = ..., status: _Optional[_Union[ProcessStatus, str]] = ..., exit_code: _Optional[int] = ..., started_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., finished_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ...

class StartProcessRequest(_message.Message):
__slots__ = ("command", "cwd", "env")
__slots__ = ("command", "cwd", "env", "process_id")
class EnvEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
Expand All @@ -69,10 +73,12 @@ class StartProcessRequest(_message.Message):
COMMAND_FIELD_NUMBER: _ClassVar[int]
CWD_FIELD_NUMBER: _ClassVar[int]
ENV_FIELD_NUMBER: _ClassVar[int]
PROCESS_ID_FIELD_NUMBER: _ClassVar[int]
command: _containers.RepeatedScalarFieldContainer[str]
cwd: str
env: _containers.ScalarMap[str, str]
def __init__(self, command: _Optional[_Iterable[str]] = ..., cwd: _Optional[str] = ..., env: _Optional[_Mapping[str, str]] = ...) -> None: ...
process_id: str
def __init__(self, command: _Optional[_Iterable[str]] = ..., cwd: _Optional[str] = ..., env: _Optional[_Mapping[str, str]] = ..., process_id: _Optional[str] = ...) -> None: ...

class StartProcessResponse(_message.Message):
__slots__ = ("process_id",)
Expand All @@ -96,7 +102,7 @@ class StreamProcessOutputsRequest(_message.Message):
stdout_offset: int
stderr_offset: int
follow: bool
def __init__(self, process_id: _Optional[str] = ..., stdout_offset: _Optional[int] = ..., stderr_offset: _Optional[int] = ..., follow: bool = ...) -> None: ...
def __init__(self, process_id: _Optional[str] = ..., stdout_offset: _Optional[int] = ..., stderr_offset: _Optional[int] = ..., follow: _Optional[bool] = ...) -> None: ...

class OutputChunk(_message.Message):
__slots__ = ("source", "data")
Expand Down Expand Up @@ -145,3 +151,13 @@ class WriteFileResponse(_message.Message):
BYTES_WRITTEN_FIELD_NUMBER: _ClassVar[int]
bytes_written: int
def __init__(self, bytes_written: _Optional[int] = ...) -> None: ...

class RemovePathRequest(_message.Message):
__slots__ = ("path", "recursive", "force")
PATH_FIELD_NUMBER: _ClassVar[int]
RECURSIVE_FIELD_NUMBER: _ClassVar[int]
FORCE_FIELD_NUMBER: _ClassVar[int]
path: str
recursive: bool
force: bool
def __init__(self, path: _Optional[str] = ..., recursive: _Optional[bool] = ..., force: _Optional[bool] = ...) -> None: ...
Loading
Loading