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.
- f_layer-2.0.0.dist-info/METADATA +232 -0
- f_layer-2.0.0.dist-info/RECORD +45 -0
- f_layer-2.0.0.dist-info/WHEEL +4 -0
- f_layer-2.0.0.dist-info/entry_points.txt +2 -0
- f_layer-2.0.0.dist-info/licenses/LICENSE +201 -0
- flayer/__init__.py +5 -0
- flayer/__main__.py +247 -0
- flayer/_vpn_guest.py +130 -0
- flayer/core/__init__.py +1 -0
- flayer/core/config.py +198 -0
- flayer/core/contracts.py +114 -0
- flayer/core/lifecycle.py +767 -0
- flayer/core/state.py +351 -0
- flayer/diagnostics/__init__.py +20 -0
- flayer/diagnostics/benchmark.py +272 -0
- flayer/diagnostics/contracts.py +167 -0
- flayer/diagnostics/health.py +154 -0
- flayer/diagnostics/reporting.py +24 -0
- flayer/extensions/__init__.py +25 -0
- flayer/extensions/builtins.py +165 -0
- flayer/extensions/contracts.py +196 -0
- flayer/extensions/discovery.py +152 -0
- flayer/extensions/registry.py +347 -0
- flayer/guest.py +270 -0
- flayer/integrations/__init__.py +23 -0
- flayer/integrations/adapters.py +92 -0
- flayer/integrations/contracts.py +241 -0
- flayer/integrations/wire.py +273 -0
- flayer/profiles/__init__.py +33 -0
- flayer/profiles/_amnezia_export.py +176 -0
- flayer/profiles/_vless_guest.py +432 -0
- flayer/profiles/amneziawg.py +555 -0
- flayer/profiles/artifacts.py +609 -0
- flayer/profiles/gateway.py +729 -0
- flayer/profiles/static_site.py +596 -0
- flayer/profiles/vless.py +422 -0
- flayer/profiles/vpn.py +524 -0
- flayer/providers/__init__.py +1 -0
- flayer/providers/contracts.py +207 -0
- flayer/providers/lifecycle.py +177 -0
- flayer/providers/yandex.py +387 -0
- flayer/providers/yandex_lifecycle.py +781 -0
- flayer/py.typed +0 -0
- flayer/vpn_export.py +72 -0
- flayer/vpn_project.py +828 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Typed, versioned extension identities and bounded credential-free contexts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import re
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from enum import StrEnum
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Protocol, runtime_checkable
|
|
12
|
+
|
|
13
|
+
from flayer.core.contracts import ContractError, StackIdentity, ValidateName
|
|
14
|
+
from flayer.profiles.artifacts import ArtifactBundle
|
|
15
|
+
from flayer.providers.contracts import CloudProvider, ValidateIdentifier
|
|
16
|
+
from flayer.providers.lifecycle import LifecycleProvider
|
|
17
|
+
|
|
18
|
+
PROVIDER_GROUP = "flayer.providers.v1"
|
|
19
|
+
PROFILE_GROUP = "flayer.profiles.v1"
|
|
20
|
+
VERSION_PATTERN = re.compile(r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\Z")
|
|
21
|
+
DISTRIBUTION_PATTERN = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*\Z")
|
|
22
|
+
TARGET_PATTERN = re.compile(
|
|
23
|
+
r"[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)*"
|
|
24
|
+
r":[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)*\Z"
|
|
25
|
+
)
|
|
26
|
+
SHA256_PATTERN = re.compile(r"[a-f0-9]{64}\Z")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ExtensionError(ContractError):
|
|
30
|
+
"""An extension violates discovery, identity, compatibility, or loading policy."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ExtensionKind(StrEnum):
|
|
34
|
+
"""The two implemented extension contracts; their ABI is encoded in the group."""
|
|
35
|
+
|
|
36
|
+
PROVIDER = "provider"
|
|
37
|
+
PROFILE = "profile"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, slots=True)
|
|
41
|
+
class ExtensionDescriptor:
|
|
42
|
+
"""A static identity; external metadata snapshots are not package authenticity proof."""
|
|
43
|
+
|
|
44
|
+
kind: ExtensionKind
|
|
45
|
+
plugin_id: str
|
|
46
|
+
distribution: str
|
|
47
|
+
version: str
|
|
48
|
+
group: str
|
|
49
|
+
target: str
|
|
50
|
+
metadata_directory: Path | None = field(default=None, repr=False)
|
|
51
|
+
metadata_sha256: str | None = field(default=None, repr=False)
|
|
52
|
+
|
|
53
|
+
def __post_init__(self) -> None:
|
|
54
|
+
"""Validate direct construction as strictly as statically decoded metadata."""
|
|
55
|
+
|
|
56
|
+
if not isinstance(self.kind, ExtensionKind):
|
|
57
|
+
raise ExtensionError("Extension kind must be an ExtensionKind member")
|
|
58
|
+
|
|
59
|
+
ValidateName(self.plugin_id, "extension.plugin_id")
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
not isinstance(self.distribution, str) or len(self.distribution) > 128
|
|
63
|
+
or DISTRIBUTION_PATTERN.fullmatch(self.distribution) is None
|
|
64
|
+
or not isinstance(self.version, str) or len(self.version) > 32
|
|
65
|
+
or VERSION_PATTERN.fullmatch(self.version) is None
|
|
66
|
+
):
|
|
67
|
+
raise ExtensionError("Extension distribution and stable release version are invalid")
|
|
68
|
+
|
|
69
|
+
expected_group = PROVIDER_GROUP if self.kind is ExtensionKind.PROVIDER else PROFILE_GROUP
|
|
70
|
+
|
|
71
|
+
if self.group != expected_group:
|
|
72
|
+
raise ExtensionError("Extension group uses an unsupported contract version")
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
not isinstance(self.target, str) or len(self.target) > 256
|
|
76
|
+
or TARGET_PATTERN.fullmatch(self.target) is None
|
|
77
|
+
):
|
|
78
|
+
raise ExtensionError("Extension target must be an exact module:attribute without extras")
|
|
79
|
+
|
|
80
|
+
if self.metadata_directory is None:
|
|
81
|
+
if self.distribution != "f-layer" or self.metadata_sha256 is not None:
|
|
82
|
+
raise ExtensionError("Only F-Layer built-ins may omit the metadata snapshot")
|
|
83
|
+
|
|
84
|
+
elif (
|
|
85
|
+
not isinstance(self.metadata_directory, Path)
|
|
86
|
+
or not self.metadata_directory.is_absolute()
|
|
87
|
+
or not self.metadata_directory.name.endswith(".dist-info")
|
|
88
|
+
or not isinstance(self.metadata_sha256, str)
|
|
89
|
+
or SHA256_PATTERN.fullmatch(self.metadata_sha256) is None
|
|
90
|
+
or self.distribution == "f-layer"
|
|
91
|
+
or self.target.split(":", 1)[0].split(".", 1)[0] == "flayer"
|
|
92
|
+
):
|
|
93
|
+
raise ExtensionError("External extensions require a valid non-reserved metadata snapshot")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True, slots=True)
|
|
97
|
+
class ProviderContext:
|
|
98
|
+
"""Explicit scope, external CLI authentication reference, and bounded execution limits."""
|
|
99
|
+
|
|
100
|
+
scope_id: str
|
|
101
|
+
authentication_reference: str
|
|
102
|
+
command_timeout: float = 45.0
|
|
103
|
+
inventory_limit: int = 10000
|
|
104
|
+
|
|
105
|
+
def __post_init__(self) -> None:
|
|
106
|
+
"""Reject credential bytes, malformed references, and unbounded execution settings."""
|
|
107
|
+
|
|
108
|
+
ValidateIdentifier(self.scope_id)
|
|
109
|
+
ValidateIdentifier(self.authentication_reference)
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
isinstance(self.command_timeout, bool)
|
|
113
|
+
or not isinstance(self.command_timeout, (int, float))
|
|
114
|
+
or not math.isfinite(self.command_timeout)
|
|
115
|
+
or not 0 < self.command_timeout <= 300
|
|
116
|
+
or type(self.inventory_limit) is not int or not 1 <= self.inventory_limit <= 100000
|
|
117
|
+
):
|
|
118
|
+
raise ExtensionError("Provider context requires bounded timeout and inventory limits")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass(frozen=True, slots=True)
|
|
122
|
+
class ProfileContext:
|
|
123
|
+
"""Exact stack ownership context; profile configuration and artifacts stay separate."""
|
|
124
|
+
|
|
125
|
+
identity: StackIdentity
|
|
126
|
+
|
|
127
|
+
def __post_init__(self) -> None:
|
|
128
|
+
"""Require the existing validated immutable ownership contract."""
|
|
129
|
+
|
|
130
|
+
if not isinstance(self.identity, StackIdentity):
|
|
131
|
+
raise ExtensionError("Profile context requires a validated StackIdentity")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@runtime_checkable
|
|
135
|
+
class ProviderExtension(Protocol):
|
|
136
|
+
"""Factory for the existing read-only provider contract, without mandatory mutation."""
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def Descriptor(self) -> ExtensionDescriptor:
|
|
140
|
+
"""Return the exact static identity registered before factory loading."""
|
|
141
|
+
|
|
142
|
+
...
|
|
143
|
+
|
|
144
|
+
def CreateProvider(self, context: ProviderContext) -> CloudProvider:
|
|
145
|
+
"""Construct a scoped read-only adapter without probing or mutating infrastructure."""
|
|
146
|
+
|
|
147
|
+
...
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@runtime_checkable
|
|
151
|
+
class LifecycleProviderExtension(ProviderExtension, Protocol):
|
|
152
|
+
"""Optional separate factory for the existing owned resource lifecycle contract."""
|
|
153
|
+
|
|
154
|
+
def CreateLifecycleProvider(self, context: ProviderContext) -> LifecycleProvider:
|
|
155
|
+
"""Construct a mutation-capable adapter without performing provider operations."""
|
|
156
|
+
|
|
157
|
+
...
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@runtime_checkable
|
|
161
|
+
class CompiledProfile(Protocol):
|
|
162
|
+
"""Validated profile adapter that preserves existing artifact and lifecycle boundaries."""
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def Identity(self) -> StackIdentity:
|
|
166
|
+
"""Return the exact identity validated during profile compilation."""
|
|
167
|
+
|
|
168
|
+
...
|
|
169
|
+
|
|
170
|
+
def BuildServerBundle(self) -> ArtifactBundle:
|
|
171
|
+
"""Generate owned server initialization bytes without writing or deploying them."""
|
|
172
|
+
|
|
173
|
+
...
|
|
174
|
+
|
|
175
|
+
def RenderPlan(self, *, user_data_file: str | Path) -> str:
|
|
176
|
+
"""Render lifecycle TOML after verifying the exact owned server artifact."""
|
|
177
|
+
|
|
178
|
+
...
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@runtime_checkable
|
|
182
|
+
class ProfileExtension(Protocol):
|
|
183
|
+
"""Compile explicit profile configuration using its own strict versioned schema."""
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def Descriptor(self) -> ExtensionDescriptor:
|
|
187
|
+
"""Return the exact static identity registered before factory loading."""
|
|
188
|
+
|
|
189
|
+
...
|
|
190
|
+
|
|
191
|
+
def CompileProfile(
|
|
192
|
+
self, configuration: Mapping[str, object], context: ProfileContext,
|
|
193
|
+
) -> CompiledProfile:
|
|
194
|
+
"""Validate configuration and exact ownership without accessing infrastructure."""
|
|
195
|
+
|
|
196
|
+
...
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Bounded static discovery from exact caller-selected installed distribution metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import configparser
|
|
6
|
+
import hashlib
|
|
7
|
+
import os
|
|
8
|
+
import stat
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
from email import policy
|
|
11
|
+
from email.parser import BytesParser
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from .contracts import (
|
|
15
|
+
PROFILE_GROUP,
|
|
16
|
+
PROVIDER_GROUP,
|
|
17
|
+
ExtensionDescriptor,
|
|
18
|
+
ExtensionError,
|
|
19
|
+
ExtensionKind,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
MAX_METADATA_BYTES = 262144
|
|
23
|
+
MAX_METADATA_DIRECTORIES = 128
|
|
24
|
+
MAX_EXTENSIONS = 256
|
|
25
|
+
METADATA_FILES = ("METADATA", "entry_points.txt", "RECORD")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _EntryPointsParser(configparser.ConfigParser):
|
|
29
|
+
"""Preserve externally declared entry-point names instead of case-folding them."""
|
|
30
|
+
|
|
31
|
+
def optionxform(self, optionstr: str) -> str:
|
|
32
|
+
"""Honor ConfigParser's callback naming contract while preserving identity."""
|
|
33
|
+
|
|
34
|
+
return optionstr
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def ReadMetadataFile(path: Path, maximum: int = MAX_METADATA_BYTES) -> bytes:
|
|
38
|
+
"""Read one regular non-linked bounded file without following a final symlink."""
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)
|
|
42
|
+
|
|
43
|
+
with os.fdopen(descriptor, "rb") as stream:
|
|
44
|
+
receipt = os.fstat(stream.fileno())
|
|
45
|
+
|
|
46
|
+
if (
|
|
47
|
+
not stat.S_ISREG(receipt.st_mode) or receipt.st_nlink != 1
|
|
48
|
+
or receipt.st_size > maximum
|
|
49
|
+
):
|
|
50
|
+
raise ExtensionError("Extension metadata or source must be a bounded regular file")
|
|
51
|
+
|
|
52
|
+
content = stream.read(maximum + 1)
|
|
53
|
+
final_receipt = os.fstat(stream.fileno())
|
|
54
|
+
|
|
55
|
+
if (
|
|
56
|
+
len(content) > maximum or len(content) != receipt.st_size
|
|
57
|
+
or (receipt.st_ino, receipt.st_size, receipt.st_mtime_ns, receipt.st_ctime_ns)
|
|
58
|
+
!= (
|
|
59
|
+
final_receipt.st_ino, final_receipt.st_size,
|
|
60
|
+
final_receipt.st_mtime_ns, final_receipt.st_ctime_ns,
|
|
61
|
+
)
|
|
62
|
+
):
|
|
63
|
+
raise ExtensionError("Extension metadata or source changed during its bounded read")
|
|
64
|
+
|
|
65
|
+
except OSError:
|
|
66
|
+
raise ExtensionError("Unable to read exact extension metadata or source") from None
|
|
67
|
+
|
|
68
|
+
return content
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def ReadMetadataSnapshot(directory: Path) -> tuple[tuple[bytes, ...], str]:
|
|
72
|
+
"""Pin only the three explicit metadata files under one selected dist-info directory."""
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
if (
|
|
76
|
+
not isinstance(directory, Path) or not directory.is_absolute()
|
|
77
|
+
or not directory.name.endswith(".dist-info") or directory.is_symlink()
|
|
78
|
+
or directory.resolve() != directory or not directory.is_dir()
|
|
79
|
+
):
|
|
80
|
+
raise ExtensionError("Discovery requires an exact absolute non-redirected dist-info directory")
|
|
81
|
+
|
|
82
|
+
except (OSError, RuntimeError):
|
|
83
|
+
raise ExtensionError("Unable to resolve exact non-redirected extension metadata") from None
|
|
84
|
+
|
|
85
|
+
contents = tuple(ReadMetadataFile(directory / name) for name in METADATA_FILES)
|
|
86
|
+
digest = hashlib.sha256()
|
|
87
|
+
|
|
88
|
+
for name, content in zip(METADATA_FILES, contents, strict=True):
|
|
89
|
+
digest.update(name.encode("ascii") + b"\x00")
|
|
90
|
+
digest.update(len(content).to_bytes(8, "big") + content)
|
|
91
|
+
|
|
92
|
+
return contents, digest.hexdigest()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def DiscoverExtensions(metadata_directories: Sequence[Path]) -> tuple[ExtensionDescriptor, ...]:
|
|
96
|
+
"""Parse selected metadata without importing code, calling finders, or searching paths."""
|
|
97
|
+
|
|
98
|
+
if (
|
|
99
|
+
not isinstance(metadata_directories, (tuple, list))
|
|
100
|
+
or len(metadata_directories) > MAX_METADATA_DIRECTORIES
|
|
101
|
+
or any(not isinstance(directory, Path) for directory in metadata_directories)
|
|
102
|
+
or len(set(metadata_directories)) != len(metadata_directories)
|
|
103
|
+
):
|
|
104
|
+
raise ExtensionError("Discovery requires bounded unique explicit metadata directories")
|
|
105
|
+
|
|
106
|
+
descriptors: list[ExtensionDescriptor] = []
|
|
107
|
+
identities: set[tuple[ExtensionKind, str]] = set()
|
|
108
|
+
|
|
109
|
+
for directory in metadata_directories:
|
|
110
|
+
contents, digest = ReadMetadataSnapshot(directory)
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
metadata = BytesParser(policy=policy.default).parsebytes(contents[0])
|
|
114
|
+
names = metadata.get_all("Name", [])
|
|
115
|
+
versions = metadata.get_all("Version", [])
|
|
116
|
+
|
|
117
|
+
if len(names) != 1 or len(versions) != 1 or metadata.defects:
|
|
118
|
+
raise ExtensionError("Distribution metadata requires one valid Name and Version")
|
|
119
|
+
|
|
120
|
+
distribution = str(names[0]).lower().replace("_", "-").replace(".", "-")
|
|
121
|
+
version = str(versions[0])
|
|
122
|
+
parser = _EntryPointsParser(interpolation=None, strict=True, delimiters=("=",))
|
|
123
|
+
parser.read_string(contents[1].decode("utf-8"))
|
|
124
|
+
|
|
125
|
+
if parser.defaults():
|
|
126
|
+
raise ExtensionError("Entry-point metadata cannot contain default entries")
|
|
127
|
+
|
|
128
|
+
for group in parser.sections():
|
|
129
|
+
if not group.startswith("flayer."):
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
if group not in {PROVIDER_GROUP, PROFILE_GROUP}:
|
|
133
|
+
raise ExtensionError("Entry-point metadata uses an unsupported F-Layer group")
|
|
134
|
+
|
|
135
|
+
kind = ExtensionKind.PROVIDER if group == PROVIDER_GROUP else ExtensionKind.PROFILE
|
|
136
|
+
|
|
137
|
+
for plugin_id, target in parser.items(group):
|
|
138
|
+
descriptor = ExtensionDescriptor(
|
|
139
|
+
kind, plugin_id, distribution, version, group, target, directory, digest,
|
|
140
|
+
)
|
|
141
|
+
identity = (kind, plugin_id)
|
|
142
|
+
|
|
143
|
+
if identity in identities or len(descriptors) >= MAX_EXTENSIONS:
|
|
144
|
+
raise ExtensionError("Extension metadata contains duplicate or excess identities")
|
|
145
|
+
|
|
146
|
+
identities.add(identity)
|
|
147
|
+
descriptors.append(descriptor)
|
|
148
|
+
|
|
149
|
+
except (UnicodeError, configparser.Error, ValueError):
|
|
150
|
+
raise ExtensionError("Extension metadata is malformed or incompatible") from None
|
|
151
|
+
|
|
152
|
+
return tuple(sorted(descriptors, key=lambda item: (item.kind.value, item.plugin_id)))
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""Explicit registry and opt-in loading of metadata-pinned, owned extension sources."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import csv
|
|
7
|
+
import hashlib
|
|
8
|
+
import importlib.abc
|
|
9
|
+
import importlib.util
|
|
10
|
+
import io
|
|
11
|
+
import sys
|
|
12
|
+
from collections.abc import Sequence
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from pathlib import Path, PurePosixPath
|
|
15
|
+
from types import ModuleType
|
|
16
|
+
|
|
17
|
+
from .builtins import BuiltinDescriptors, LoadBuiltin
|
|
18
|
+
from .contracts import (
|
|
19
|
+
ExtensionDescriptor,
|
|
20
|
+
ExtensionError,
|
|
21
|
+
ExtensionKind,
|
|
22
|
+
ProfileExtension,
|
|
23
|
+
ProviderExtension,
|
|
24
|
+
)
|
|
25
|
+
from .discovery import (
|
|
26
|
+
MAX_EXTENSIONS,
|
|
27
|
+
DiscoverExtensions,
|
|
28
|
+
ReadMetadataFile,
|
|
29
|
+
ReadMetadataSnapshot,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
MAX_MODULE_BYTES = 1048576
|
|
33
|
+
MAX_MODULE_DEPTH = 16
|
|
34
|
+
_LOADED_SOURCES: dict[str, tuple[ModuleType, Path, str]] = {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class _ModuleSource:
|
|
39
|
+
"""Verified immutable Python bytes owned by one selected distribution RECORD."""
|
|
40
|
+
|
|
41
|
+
name: str
|
|
42
|
+
path: Path
|
|
43
|
+
content: bytes = field(repr=False)
|
|
44
|
+
package: bool = False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _SnapshotImporter(importlib.abc.MetaPathFinder, importlib.abc.Loader):
|
|
48
|
+
"""Serve only already verified selected-chain imports from immutable source snapshots."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self, sources: tuple[_ModuleSource, ...], created: list[tuple[str, ModuleType]],
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Bind the exact source chain and cleanup receipts for one explicit load attempt."""
|
|
54
|
+
|
|
55
|
+
self._sources = {source.name: source for source in sources}
|
|
56
|
+
self._created = created
|
|
57
|
+
|
|
58
|
+
def find_spec(
|
|
59
|
+
self, fullname: str, path: Sequence[str] | None, target: ModuleType | None = None,
|
|
60
|
+
) -> importlib.machinery.ModuleSpec | None:
|
|
61
|
+
"""Honor Python's finder callback only for exact selected source identities."""
|
|
62
|
+
|
|
63
|
+
source = self._sources.get(fullname)
|
|
64
|
+
|
|
65
|
+
if source is None:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
return importlib.util.spec_from_file_location(
|
|
69
|
+
fullname, source.path, loader=self,
|
|
70
|
+
submodule_search_locations=[str(source.path.parent)] if source.package else None,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def create_module(self, spec: importlib.machinery.ModuleSpec) -> ModuleType | None:
|
|
74
|
+
"""Honor Python's loader callback with the standard module construction behavior."""
|
|
75
|
+
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
def exec_module(self, module: ModuleType) -> None:
|
|
79
|
+
"""Execute selected verified bytes and register exact module-object provenance."""
|
|
80
|
+
|
|
81
|
+
source = self._sources[module.__name__]
|
|
82
|
+
self._created.append((source.name, module))
|
|
83
|
+
exec(compile(source.content, str(source.path), "exec"), module.__dict__)
|
|
84
|
+
_LOADED_SOURCES[source.name] = (
|
|
85
|
+
module, source.path, hashlib.sha256(source.content).hexdigest(),
|
|
86
|
+
)
|
|
87
|
+
_RequireReceipt(source, module)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _ReadSources(descriptor: ExtensionDescriptor) -> tuple[_ModuleSource, ...]:
|
|
91
|
+
"""Validate exact metadata and every parent/target source before any plugin execution."""
|
|
92
|
+
|
|
93
|
+
directory = descriptor.metadata_directory
|
|
94
|
+
|
|
95
|
+
if directory is None:
|
|
96
|
+
raise ExtensionError("External loading requires a selected metadata directory")
|
|
97
|
+
|
|
98
|
+
contents, digest = ReadMetadataSnapshot(directory)
|
|
99
|
+
|
|
100
|
+
if digest != descriptor.metadata_sha256 or descriptor not in DiscoverExtensions((directory,)):
|
|
101
|
+
raise ExtensionError("Selected extension metadata changed after discovery")
|
|
102
|
+
|
|
103
|
+
records: dict[str, tuple[str, str]] = {}
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
for row in csv.reader(io.StringIO(contents[2].decode("utf-8")), strict=True):
|
|
107
|
+
if (
|
|
108
|
+
len(row) != 3 or not row[0] or row[0] in records
|
|
109
|
+
or PurePosixPath(row[0]).is_absolute() or "\\" in row[0]
|
|
110
|
+
):
|
|
111
|
+
raise ExtensionError("Distribution RECORD contains malformed or duplicate paths")
|
|
112
|
+
|
|
113
|
+
records[row[0]] = (row[1], row[2])
|
|
114
|
+
|
|
115
|
+
except (UnicodeError, csv.Error):
|
|
116
|
+
raise ExtensionError("Distribution RECORD is malformed") from None
|
|
117
|
+
|
|
118
|
+
module = descriptor.target.split(":", 1)[0]
|
|
119
|
+
parts = module.split(".")
|
|
120
|
+
|
|
121
|
+
if len(parts) > MAX_MODULE_DEPTH:
|
|
122
|
+
raise ExtensionError("Extension import target exceeds the supported package depth")
|
|
123
|
+
|
|
124
|
+
sources: list[_ModuleSource] = []
|
|
125
|
+
|
|
126
|
+
for index in range(1, len(parts) + 1):
|
|
127
|
+
stem = "/".join(parts[:index])
|
|
128
|
+
candidates: tuple[str, ...] = (stem + "/__init__.py",)
|
|
129
|
+
|
|
130
|
+
if index == len(parts):
|
|
131
|
+
candidates += (stem + ".py",)
|
|
132
|
+
|
|
133
|
+
matches = tuple(candidate for candidate in candidates if candidate in records)
|
|
134
|
+
|
|
135
|
+
if len(matches) != 1:
|
|
136
|
+
raise ExtensionError("Extension target and parent packages require exact RECORD ownership")
|
|
137
|
+
|
|
138
|
+
relative = matches[0]
|
|
139
|
+
path = directory.parent / relative
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
if path.resolve() != path or not path.is_relative_to(directory.parent):
|
|
143
|
+
raise ExtensionError("Extension source cannot redirect outside its selected installation")
|
|
144
|
+
|
|
145
|
+
except (OSError, RuntimeError):
|
|
146
|
+
raise ExtensionError("Unable to resolve exact non-redirected extension source") from None
|
|
147
|
+
|
|
148
|
+
content = ReadMetadataFile(path, MAX_MODULE_BYTES)
|
|
149
|
+
source_digest = base64.urlsafe_b64encode(hashlib.sha256(content).digest()).decode().rstrip("=")
|
|
150
|
+
|
|
151
|
+
if records[relative] != ("sha256=" + source_digest, str(len(content))):
|
|
152
|
+
raise ExtensionError("Extension source does not match its exact RECORD hash and size")
|
|
153
|
+
|
|
154
|
+
sources.append(_ModuleSource(
|
|
155
|
+
".".join(parts[:index]), path, content, relative.endswith("/__init__.py"),
|
|
156
|
+
))
|
|
157
|
+
|
|
158
|
+
return tuple(sources)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _LoadFactory(descriptor: ExtensionDescriptor) -> ProviderExtension | ProfileExtension:
|
|
162
|
+
"""Execute verified selected sources directly, rejecting ambient module shadowing."""
|
|
163
|
+
|
|
164
|
+
sources = _ReadSources(descriptor)
|
|
165
|
+
|
|
166
|
+
for source in sources:
|
|
167
|
+
existing = sys.modules.get(source.name)
|
|
168
|
+
|
|
169
|
+
if existing is not None:
|
|
170
|
+
_RequireReceipt(source, existing)
|
|
171
|
+
|
|
172
|
+
created: list[tuple[str, ModuleType]] = []
|
|
173
|
+
importer = _SnapshotImporter(sources, created)
|
|
174
|
+
sys.meta_path.insert(0, importer)
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
for source in sources:
|
|
178
|
+
existing = sys.modules.get(source.name)
|
|
179
|
+
|
|
180
|
+
if existing is not None:
|
|
181
|
+
_RequireReceipt(source, existing)
|
|
182
|
+
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
spec = importer.find_spec(source.name, None)
|
|
186
|
+
|
|
187
|
+
if spec is None:
|
|
188
|
+
raise ExtensionError("Unable to construct the selected extension module")
|
|
189
|
+
|
|
190
|
+
module = importlib.util.module_from_spec(spec)
|
|
191
|
+
sys.modules[source.name] = module
|
|
192
|
+
importer.exec_module(module)
|
|
193
|
+
|
|
194
|
+
if "." in source.name:
|
|
195
|
+
parent_name, _, child_name = source.name.rpartition(".")
|
|
196
|
+
setattr(sys.modules[parent_name], child_name, module)
|
|
197
|
+
|
|
198
|
+
factory: object = sys.modules[sources[-1].name]
|
|
199
|
+
|
|
200
|
+
for attribute in descriptor.target.split(":", 1)[1].split("."):
|
|
201
|
+
factory = getattr(factory, attribute)
|
|
202
|
+
|
|
203
|
+
if not callable(factory):
|
|
204
|
+
raise ExtensionError("Selected entry-point target is not a callable factory")
|
|
205
|
+
|
|
206
|
+
return _ValidateExtension(factory(), descriptor)
|
|
207
|
+
|
|
208
|
+
except (Exception, SystemExit):
|
|
209
|
+
for name, module in reversed(created):
|
|
210
|
+
if sys.modules.get(name) is module:
|
|
211
|
+
del sys.modules[name]
|
|
212
|
+
|
|
213
|
+
receipt = _LOADED_SOURCES.get(name)
|
|
214
|
+
|
|
215
|
+
if receipt is not None and receipt[0] is module:
|
|
216
|
+
del _LOADED_SOURCES[name]
|
|
217
|
+
|
|
218
|
+
if "." in name:
|
|
219
|
+
parent_name, _, child_name = name.rpartition(".")
|
|
220
|
+
parent = sys.modules.get(parent_name)
|
|
221
|
+
|
|
222
|
+
if parent is not None and parent.__dict__.get(child_name) is module:
|
|
223
|
+
del parent.__dict__[child_name]
|
|
224
|
+
|
|
225
|
+
raise ExtensionError("Selected extension factory failed to load") from None
|
|
226
|
+
|
|
227
|
+
finally:
|
|
228
|
+
if importer in sys.meta_path:
|
|
229
|
+
sys.meta_path.remove(importer)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _RequireReceipt(source: _ModuleSource, module: ModuleType) -> None:
|
|
233
|
+
"""Reject cached code unless this loader executed this exact verified byte snapshot."""
|
|
234
|
+
|
|
235
|
+
receipt = _LOADED_SOURCES.get(source.name)
|
|
236
|
+
|
|
237
|
+
if (
|
|
238
|
+
receipt is None or receipt[0] is not module or receipt[1] != source.path
|
|
239
|
+
or receipt[2] != hashlib.sha256(source.content).hexdigest()
|
|
240
|
+
or sys.modules.get(source.name) is not module
|
|
241
|
+
or module.__dict__.get("__file__") != str(source.path)
|
|
242
|
+
or getattr(module.__dict__.get("__spec__"), "origin", None) != str(source.path)
|
|
243
|
+
):
|
|
244
|
+
raise ExtensionError("An unverified cached module shadows the selected extension source")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _ValidateExtension(
|
|
248
|
+
loaded: object, descriptor: ExtensionDescriptor,
|
|
249
|
+
) -> ProviderExtension | ProfileExtension:
|
|
250
|
+
"""Check exact identity and callable protocol before a factory transaction commits."""
|
|
251
|
+
|
|
252
|
+
expected = ProviderExtension if descriptor.kind is ExtensionKind.PROVIDER else ProfileExtension
|
|
253
|
+
method = "CreateProvider" if descriptor.kind is ExtensionKind.PROVIDER else "CompileProfile"
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
if (
|
|
257
|
+
not isinstance(loaded, expected) or loaded.Descriptor != descriptor
|
|
258
|
+
or not callable(getattr(loaded, method, None))
|
|
259
|
+
):
|
|
260
|
+
raise ExtensionError("Loaded extension does not match its declared identity and contract")
|
|
261
|
+
|
|
262
|
+
except (Exception, SystemExit):
|
|
263
|
+
raise ExtensionError("Loaded extension does not match its declared identity and contract") from None
|
|
264
|
+
|
|
265
|
+
return loaded
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class ExtensionRegistry:
|
|
269
|
+
"""A bounded explicit catalog; neither construction nor lookup imports external code."""
|
|
270
|
+
|
|
271
|
+
def __init__(
|
|
272
|
+
self, descriptors: Sequence[ExtensionDescriptor] = (), *, include_builtins: bool = True,
|
|
273
|
+
) -> None:
|
|
274
|
+
"""Validate complete registration before publishing any catalog entries."""
|
|
275
|
+
|
|
276
|
+
if type(include_builtins) is not bool or not isinstance(descriptors, (tuple, list)):
|
|
277
|
+
raise ExtensionError("Registry requires explicit bounded descriptor registration")
|
|
278
|
+
|
|
279
|
+
entries = (*BuiltinDescriptors(), *descriptors) if include_builtins else tuple(descriptors)
|
|
280
|
+
|
|
281
|
+
if len(entries) > MAX_EXTENSIONS:
|
|
282
|
+
raise ExtensionError("Registry exceeds its extension bound")
|
|
283
|
+
|
|
284
|
+
registered: dict[tuple[ExtensionKind, str], ExtensionDescriptor] = {}
|
|
285
|
+
|
|
286
|
+
for descriptor in entries:
|
|
287
|
+
if not isinstance(descriptor, ExtensionDescriptor):
|
|
288
|
+
raise ExtensionError("Registry accepts only validated ExtensionDescriptor objects")
|
|
289
|
+
|
|
290
|
+
identity = (descriptor.kind, descriptor.plugin_id)
|
|
291
|
+
|
|
292
|
+
if identity in registered:
|
|
293
|
+
raise ExtensionError("Duplicate extension identity would shadow a registered extension")
|
|
294
|
+
|
|
295
|
+
if descriptor.metadata_directory is None and descriptor not in BuiltinDescriptors():
|
|
296
|
+
raise ExtensionError("Registry rejects invented or incompatible built-in identities")
|
|
297
|
+
|
|
298
|
+
registered[identity] = descriptor
|
|
299
|
+
|
|
300
|
+
self._descriptors = registered
|
|
301
|
+
self._loaded: dict[ExtensionDescriptor, ProviderExtension | ProfileExtension] = {}
|
|
302
|
+
|
|
303
|
+
def Descriptors(self) -> tuple[ExtensionDescriptor, ...]:
|
|
304
|
+
"""Return immutable descriptors in deterministic kind and identifier order."""
|
|
305
|
+
|
|
306
|
+
return tuple(sorted(self._descriptors.values(), key=lambda item: (item.kind.value, item.plugin_id)))
|
|
307
|
+
|
|
308
|
+
def Get(self, kind: ExtensionKind, plugin_id: str) -> ExtensionDescriptor:
|
|
309
|
+
"""Select one registered identity without imports or capability probes."""
|
|
310
|
+
|
|
311
|
+
if not isinstance(kind, ExtensionKind) or not isinstance(plugin_id, str):
|
|
312
|
+
raise ExtensionError("Registry lookup requires an exact extension kind and identifier")
|
|
313
|
+
|
|
314
|
+
descriptor = self._descriptors.get((kind, plugin_id))
|
|
315
|
+
|
|
316
|
+
if descriptor is None:
|
|
317
|
+
raise ExtensionError("Extension identity is not registered")
|
|
318
|
+
|
|
319
|
+
return descriptor
|
|
320
|
+
|
|
321
|
+
def Load(
|
|
322
|
+
self, descriptor: ExtensionDescriptor, *, allowed: Sequence[ExtensionDescriptor] = (),
|
|
323
|
+
) -> ProviderExtension | ProfileExtension:
|
|
324
|
+
"""Load an exact registered factory; external code requires an exact descriptor pin."""
|
|
325
|
+
|
|
326
|
+
if (
|
|
327
|
+
not isinstance(descriptor, ExtensionDescriptor)
|
|
328
|
+
or self.Get(descriptor.kind, descriptor.plugin_id) != descriptor
|
|
329
|
+
or not isinstance(allowed, (tuple, list)) or len(allowed) > MAX_EXTENSIONS
|
|
330
|
+
or any(not isinstance(item, ExtensionDescriptor) for item in allowed)
|
|
331
|
+
or len(set(allowed)) != len(allowed)
|
|
332
|
+
):
|
|
333
|
+
raise ExtensionError("Loading requires an exact registered descriptor and bounded unique pins")
|
|
334
|
+
|
|
335
|
+
if descriptor.metadata_directory is not None and descriptor not in allowed:
|
|
336
|
+
raise ExtensionError("External loading requires an explicit exact descriptor allowlist")
|
|
337
|
+
|
|
338
|
+
if descriptor in self._loaded:
|
|
339
|
+
return self._loaded[descriptor]
|
|
340
|
+
|
|
341
|
+
loaded = (
|
|
342
|
+
_ValidateExtension(LoadBuiltin(descriptor), descriptor)
|
|
343
|
+
if descriptor.metadata_directory is None else _LoadFactory(descriptor)
|
|
344
|
+
)
|
|
345
|
+
self._loaded[descriptor] = loaded
|
|
346
|
+
|
|
347
|
+
return loaded
|