f-layer 2.0.0__py3-none-any.whl

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.
Files changed (45) hide show
  1. f_layer-2.0.0.dist-info/METADATA +232 -0
  2. f_layer-2.0.0.dist-info/RECORD +45 -0
  3. f_layer-2.0.0.dist-info/WHEEL +4 -0
  4. f_layer-2.0.0.dist-info/entry_points.txt +2 -0
  5. f_layer-2.0.0.dist-info/licenses/LICENSE +201 -0
  6. flayer/__init__.py +5 -0
  7. flayer/__main__.py +247 -0
  8. flayer/_vpn_guest.py +130 -0
  9. flayer/core/__init__.py +1 -0
  10. flayer/core/config.py +198 -0
  11. flayer/core/contracts.py +114 -0
  12. flayer/core/lifecycle.py +767 -0
  13. flayer/core/state.py +351 -0
  14. flayer/diagnostics/__init__.py +20 -0
  15. flayer/diagnostics/benchmark.py +272 -0
  16. flayer/diagnostics/contracts.py +167 -0
  17. flayer/diagnostics/health.py +154 -0
  18. flayer/diagnostics/reporting.py +24 -0
  19. flayer/extensions/__init__.py +25 -0
  20. flayer/extensions/builtins.py +165 -0
  21. flayer/extensions/contracts.py +196 -0
  22. flayer/extensions/discovery.py +152 -0
  23. flayer/extensions/registry.py +347 -0
  24. flayer/guest.py +270 -0
  25. flayer/integrations/__init__.py +23 -0
  26. flayer/integrations/adapters.py +92 -0
  27. flayer/integrations/contracts.py +241 -0
  28. flayer/integrations/wire.py +273 -0
  29. flayer/profiles/__init__.py +33 -0
  30. flayer/profiles/_amnezia_export.py +176 -0
  31. flayer/profiles/_vless_guest.py +432 -0
  32. flayer/profiles/amneziawg.py +555 -0
  33. flayer/profiles/artifacts.py +609 -0
  34. flayer/profiles/gateway.py +729 -0
  35. flayer/profiles/static_site.py +596 -0
  36. flayer/profiles/vless.py +422 -0
  37. flayer/profiles/vpn.py +524 -0
  38. flayer/providers/__init__.py +1 -0
  39. flayer/providers/contracts.py +207 -0
  40. flayer/providers/lifecycle.py +177 -0
  41. flayer/providers/yandex.py +387 -0
  42. flayer/providers/yandex_lifecycle.py +781 -0
  43. flayer/py.typed +0 -0
  44. flayer/vpn_export.py +72 -0
  45. flayer/vpn_project.py +828 -0
@@ -0,0 +1,207 @@
1
+ """Provider-neutral contracts for bounded, read-only infrastructure discovery."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass
7
+ from enum import StrEnum
8
+ from typing import Protocol, runtime_checkable
9
+
10
+ IDENTIFIER_PATTERN = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,127}\Z")
11
+
12
+
13
+ class ResourceKind(StrEnum):
14
+ """Infrastructure resource kinds supported by the discovery contract."""
15
+
16
+ INSTANCE = "instance"
17
+ DISK = "disk"
18
+ NETWORK = "network"
19
+ SUBNET = "subnet"
20
+ ADDRESS = "address"
21
+ SECURITY_GROUP = "security_group"
22
+
23
+
24
+ class ProviderCapability(StrEnum):
25
+ """Implemented operations advertised by a provider, independent of permissions."""
26
+
27
+ INVENTORY = "inventory"
28
+ RESOURCE_LOOKUP = "resource_lookup"
29
+ CREATE_RESOURCE = "create_resource"
30
+ DELETE_RESOURCE = "delete_resource"
31
+
32
+
33
+ class ProviderErrorCode(StrEnum):
34
+ """Stable failure categories that never include vendor output or credentials."""
35
+
36
+ UNAVAILABLE = "unavailable"
37
+ AUTHENTICATION = "authentication"
38
+ PERMISSION_DENIED = "permission_denied"
39
+ NOT_FOUND = "not_found"
40
+ TIMEOUT = "timeout"
41
+ THROTTLED = "throttled"
42
+ CONFLICT = "conflict"
43
+ SCOPE_MISMATCH = "scope_mismatch"
44
+ INVALID_RESPONSE = "invalid_response"
45
+ UNSUPPORTED = "unsupported"
46
+ COMMAND_FAILED = "command_failed"
47
+ INCOMPLETE_INVENTORY = "incomplete_inventory"
48
+
49
+
50
+ class ProviderError(RuntimeError):
51
+ """A sanitized provider failure with a stable code and retry recommendation."""
52
+
53
+ def __init__(self, code: ProviderErrorCode, operation: str) -> None:
54
+ """Build an error without retaining external command output or exceptions."""
55
+
56
+ self.code = code
57
+ self.operation = operation
58
+ self.retryable = code in {
59
+ ProviderErrorCode.TIMEOUT,
60
+ ProviderErrorCode.THROTTLED,
61
+ }
62
+ super().__init__(f"Provider operation {operation} failed: {code.value}")
63
+
64
+
65
+ def ValidateIdentifier(value: str) -> None:
66
+ """Reject unsafe identifiers without including the rejected value in errors."""
67
+
68
+ if not isinstance(value, str) or IDENTIFIER_PATTERN.fullmatch(value) is None:
69
+ raise ValueError(
70
+ "Provider identifiers require 1-128 ASCII letters, digits, dots, underscores or dashes"
71
+ )
72
+
73
+
74
+ @dataclass(frozen=True, slots=True)
75
+ class ProviderIdentity:
76
+ """Non-secret provider and explicit scope identity; credentials remain external."""
77
+
78
+ provider_id: str
79
+ scope_id: str
80
+ authentication_source: str
81
+
82
+ def __post_init__(self) -> None:
83
+ """Validate immutable identity fields before they can define a resource scope."""
84
+
85
+ ValidateIdentifier(self.provider_id)
86
+ ValidateIdentifier(self.scope_id)
87
+ ValidateIdentifier(self.authentication_source)
88
+
89
+
90
+ @dataclass(frozen=True, slots=True)
91
+ class ResourceReference:
92
+ """A resource key bound to one provider, one scope and one resource kind."""
93
+
94
+ provider_id: str
95
+ scope_id: str
96
+ kind: ResourceKind
97
+ resource_id: str
98
+
99
+ def __post_init__(self) -> None:
100
+ """Reject malformed identities and unrecognized resource kinds."""
101
+
102
+ ValidateIdentifier(self.provider_id)
103
+ ValidateIdentifier(self.scope_id)
104
+ ValidateIdentifier(self.resource_id)
105
+
106
+ if not isinstance(self.kind, ResourceKind):
107
+ raise ValueError("Resource kind must be a ResourceKind member")
108
+
109
+
110
+ @dataclass(frozen=True, slots=True)
111
+ class ProviderResource:
112
+ """A normalized observation, excluding raw vendor payloads and instance metadata."""
113
+
114
+ reference: ResourceReference
115
+ name: str
116
+ status: str = "UNKNOWN"
117
+ zone_id: str | None = None
118
+ labels: tuple[tuple[str, str], ...] = ()
119
+ public_addresses: tuple[str, ...] = ()
120
+
121
+ def __post_init__(self) -> None:
122
+ """Require immutable labels and endpoint collections from provider adapters."""
123
+
124
+ if not isinstance(self.reference, ResourceReference):
125
+ raise ValueError("Provider resources require a validated resource reference")
126
+
127
+ if not isinstance(self.name, str) or not isinstance(self.status, str):
128
+ raise ValueError("Resource names and statuses must be strings")
129
+
130
+ if self.zone_id is not None:
131
+ ValidateIdentifier(self.zone_id)
132
+
133
+ if (
134
+ not isinstance(self.labels, tuple)
135
+ or any(
136
+ not isinstance(label, tuple)
137
+ or len(label) != 2
138
+ or not all(isinstance(value, str) for value in label)
139
+ for label in self.labels
140
+ )
141
+ or len({label[0] for label in self.labels}) != len(self.labels)
142
+ ):
143
+ raise ValueError("Resource labels must be immutable unique string pairs")
144
+
145
+ if not isinstance(self.public_addresses, tuple) or not all(
146
+ isinstance(address, str) for address in self.public_addresses
147
+ ):
148
+ raise ValueError("Public addresses must be an immutable string tuple")
149
+
150
+ def HasLabels(self, expected: tuple[tuple[str, str], ...]) -> bool:
151
+ """Check all required labels without treating a resource name as ownership."""
152
+
153
+ actual = dict(self.labels)
154
+
155
+ return bool(expected) and all(actual.get(key) == value for key, value in expected)
156
+
157
+
158
+ @dataclass(frozen=True, slots=True)
159
+ class ProviderStatus:
160
+ """A sanitized probe result; unknown authentication differs from a failed probe."""
161
+
162
+ identity: ProviderIdentity
163
+ available: bool
164
+ authenticated: bool | None = None
165
+ error_code: ProviderErrorCode | None = None
166
+
167
+
168
+ @runtime_checkable
169
+ class CloudProvider(Protocol):
170
+ """Read-only provider boundary consumed by diagnostics and future orchestration."""
171
+
172
+ @property
173
+ def Identity(self) -> ProviderIdentity:
174
+ """Return the provider identity and explicit discovery scope."""
175
+
176
+ ...
177
+
178
+ @property
179
+ def Capabilities(self) -> frozenset[ProviderCapability]:
180
+ """Advertise implemented operations without promising account authorization."""
181
+
182
+ ...
183
+
184
+ def CheckAvailability(self) -> ProviderStatus:
185
+ """Probe the local provider adapter without accessing infrastructure."""
186
+
187
+ ...
188
+
189
+ def CheckAuthentication(self) -> ProviderStatus:
190
+ """Probe read permission to the selected scope without exposing credentials."""
191
+
192
+ ...
193
+
194
+ def ListResources(self, kind: ResourceKind) -> tuple[ProviderResource, ...]:
195
+ """List a complete resource kind within the provider's explicit scope."""
196
+
197
+ ...
198
+
199
+ def GetResource(self, reference: ResourceReference) -> ProviderResource:
200
+ """Read one resource and reject any reference or response outside the scope."""
201
+
202
+ ...
203
+
204
+ def DiscoverInventory(self) -> tuple[ProviderResource, ...]:
205
+ """Return all supported kinds or fail without returning partial inventory."""
206
+
207
+ ...
@@ -0,0 +1,177 @@
1
+ """Immutable lifecycle requests and explicit, ownership-bound mutation contracts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ from collections.abc import Mapping
8
+ from dataclasses import dataclass, field
9
+ from typing import Protocol, TypeAlias, runtime_checkable
10
+
11
+ from flayer.core.contracts import StackIdentity, ValidateName
12
+
13
+ from .contracts import (
14
+ ProviderCapability,
15
+ ProviderError,
16
+ ProviderErrorCode,
17
+ ProviderIdentity,
18
+ ProviderResource,
19
+ ResourceKind,
20
+ ResourceReference,
21
+ )
22
+
23
+ JsonValue: TypeAlias = str | int | bool | None | tuple["JsonValue", ...]
24
+ MAX_PARAMETER_DEPTH = 10
25
+ MAX_PARAMETER_VALUES = 2048
26
+ LOGICAL_ID_LABEL = "flayer-resource"
27
+ SPEC_DIGEST_LABEL = "flayer-spec"
28
+ OPERATION_ID_LABEL = "flayer-operation"
29
+
30
+
31
+ def _ValidateValue(value: JsonValue, depth: int, remaining: list[int]) -> None:
32
+ """Bound recursively immutable values before hashing or translating requests."""
33
+
34
+ remaining[0] -= 1
35
+
36
+ if depth > MAX_PARAMETER_DEPTH or remaining[0] < 0:
37
+ raise ValueError("Resource parameters exceed their structural bounds")
38
+
39
+ if isinstance(value, tuple):
40
+ for item in value:
41
+ _ValidateValue(item, depth + 1, remaining)
42
+
43
+ elif isinstance(value, str):
44
+ if len(value) > 16384 or "\x00" in value:
45
+ raise ValueError("Resource parameter strings exceed their bounds")
46
+
47
+ elif value is not None and type(value) not in {int, bool}:
48
+ raise ValueError("Resource parameters require immutable JSON-compatible values")
49
+
50
+ elif type(value) is int and not -(2 ** 63) <= value < 2 ** 63:
51
+ raise ValueError("Resource integer parameters exceed signed 64-bit bounds")
52
+
53
+
54
+ @dataclass(frozen=True, slots=True)
55
+ class ResourceSpec:
56
+ """A desired logical resource with immutable options and explicit dependencies."""
57
+
58
+ logical_id: str
59
+ kind: ResourceKind
60
+ name: str
61
+ parameters: tuple[tuple[str, JsonValue], ...] = field(default=(), repr=False)
62
+ dependencies: tuple[str, ...] = ()
63
+
64
+ def __post_init__(self) -> None:
65
+ """Reject malformed, mutable, duplicate or cyclic direct request fields."""
66
+
67
+ ValidateName(self.logical_id, "resource.logical_id")
68
+ ValidateName(self.name, "resource.name")
69
+
70
+ if not isinstance(self.kind, ResourceKind):
71
+ raise ValueError("Lifecycle kind requires a ResourceKind member")
72
+
73
+ if (
74
+ not isinstance(self.parameters, tuple)
75
+ or len(self.parameters) > 128
76
+ or any(
77
+ not isinstance(pair, tuple) or len(pair) != 2
78
+ or not isinstance(pair[0], str) or not pair[0].isidentifier()
79
+ for pair in self.parameters
80
+ )
81
+ or len({pair[0] for pair in self.parameters}) != len(self.parameters)
82
+ ):
83
+ raise ValueError("Resource parameters require unique immutable named pairs")
84
+
85
+ remaining = [MAX_PARAMETER_VALUES]
86
+
87
+ for _, value in self.parameters:
88
+ _ValidateValue(value, 0, remaining)
89
+
90
+ if (
91
+ not isinstance(self.dependencies, tuple)
92
+ or len(self.dependencies) > 128
93
+ or any(not isinstance(value, str) for value in self.dependencies)
94
+ or len(set(self.dependencies)) != len(self.dependencies)
95
+ or self.logical_id in self.dependencies
96
+ ):
97
+ raise ValueError("Resource dependencies require unique immutable logical IDs")
98
+
99
+ for logical_id in self.dependencies:
100
+ ValidateName(logical_id, "resource.dependency")
101
+
102
+ def OwnershipLabels(self, identity: StackIdentity) -> tuple[tuple[str, str], ...]:
103
+ """Bind one logical resource to the complete stack ownership boundary."""
104
+
105
+ labels = identity.OwnershipLabels()
106
+ labels[LOGICAL_ID_LABEL] = self.logical_id
107
+ content = (self.logical_id, self.kind.value, self.name, self.parameters, self.dependencies)
108
+ digest = hashlib.sha256(json.dumps(content, sort_keys=True).encode("utf-8")).hexdigest()
109
+ labels[SPEC_DIGEST_LABEL] = digest[:40]
110
+
111
+ return tuple(sorted(labels.items()))
112
+
113
+
114
+ class MutationError(ProviderError):
115
+ """A sanitized mutation failure that distinguishes an uncertain remote outcome."""
116
+
117
+ def __init__(
118
+ self, code: ProviderErrorCode, operation: str, outcome_unknown: bool
119
+ ) -> None:
120
+ """Retain only a stable failure category and whether submission may have occurred."""
121
+
122
+ if type(outcome_unknown) is not bool:
123
+ raise ValueError("Mutation outcome uncertainty requires a boolean")
124
+
125
+ self.outcome_unknown = outcome_unknown
126
+ super().__init__(code, operation)
127
+ self.retryable = self.retryable and not outcome_unknown
128
+
129
+
130
+ @runtime_checkable
131
+ class LifecycleProvider(Protocol):
132
+ """Mutation boundary with exact scope, stable reconciliation and owned deletion."""
133
+
134
+ @property
135
+ def Identity(self) -> ProviderIdentity:
136
+ """Return the provider and explicit scope used by every observation and command."""
137
+
138
+ ...
139
+
140
+ @property
141
+ def Capabilities(self) -> frozenset[ProviderCapability]:
142
+ """Advertise implemented mutation operations independently of account permissions."""
143
+
144
+ ...
145
+
146
+ def ValidateSpec(self, spec: ResourceSpec) -> None:
147
+ """Reject unsupported resource options without accessing infrastructure."""
148
+
149
+ ...
150
+
151
+ def FindResource(
152
+ self, spec: ResourceSpec, identity: StackIdentity
153
+ ) -> ProviderResource | None:
154
+ """Find the unique complete ownership-label match or reject ambiguous inventory."""
155
+
156
+ ...
157
+
158
+ def GetResource(self, reference: ResourceReference) -> ProviderResource:
159
+ """Observe an exact reference with scope validation and sanitized failures."""
160
+
161
+ ...
162
+
163
+ def CreateResource(
164
+ self, spec: ResourceSpec, identity: StackIdentity,
165
+ dependencies: Mapping[str, ProviderResource], operation_id: str,
166
+ ) -> ProviderResource:
167
+ """Create once from validated owned dependencies; never retry uncertain creation."""
168
+
169
+ ...
170
+
171
+ def DeleteResource(
172
+ self, reference: ResourceReference, identity: StackIdentity, logical_id: str,
173
+ operation_id: str | None = None,
174
+ ) -> None:
175
+ """Re-read exact scope and complete ownership immediately before deleting by ID."""
176
+
177
+ ...