cf-bootstrap-source-local 0.1.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.
- cf_bootstrap_source_local/__init__.py +20 -0
- cf_bootstrap_source_local/artifacts.py +191 -0
- cf_bootstrap_source_local/build.py +90 -0
- cf_bootstrap_source_local/capabilities.py +91 -0
- cf_bootstrap_source_local/inventory.py +255 -0
- cf_bootstrap_source_local/model.py +91 -0
- cf_bootstrap_source_local/native.py +163 -0
- cf_bootstrap_source_local/profiles.py +129 -0
- cf_bootstrap_source_local/resolver.py +573 -0
- cf_bootstrap_source_local/semantics/package.trig +13 -0
- cf_bootstrap_source_local-0.1.0.dist-info/METADATA +27 -0
- cf_bootstrap_source_local-0.1.0.dist-info/RECORD +14 -0
- cf_bootstrap_source_local-0.1.0.dist-info/WHEEL +4 -0
- cf_bootstrap_source_local-0.1.0.dist-info/entry_points.txt +8 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
"""JSON-lines entry point for deterministic local artifact resolution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import platform
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import tempfile
|
|
13
|
+
import hashlib
|
|
14
|
+
from cf_bootstrap_source_pypi.release_authority import canonical as release_canonical, operation_catalog, select as release_select, target_environment as release_environment
|
|
15
|
+
|
|
16
|
+
from .artifacts import _safe_zip_name, _validate_record, native_authority, native_executable_sha256, validate_wheel, validate_native_wheel
|
|
17
|
+
from .build import build
|
|
18
|
+
from .inventory import dependency_closure, inventory, inventory_natives
|
|
19
|
+
from .native import _platform, package_native
|
|
20
|
+
from .model import PROTOCOL, REQUEST_SCHEMA, RESULT_SCHEMA, ResolverError, canonical_json, parse_request
|
|
21
|
+
from .capabilities import discover
|
|
22
|
+
from .profiles import load_catalog
|
|
23
|
+
from packaging.requirements import Requirement
|
|
24
|
+
from packaging.version import Version, InvalidVersion
|
|
25
|
+
|
|
26
|
+
_FIELDS = {"jsonrpc", "protocol", "id", "method", "params"}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _stable_report(value: object) -> object:
|
|
30
|
+
if isinstance(value, dict):
|
|
31
|
+
return {key: _stable_report(item) for key, item in value.items()}
|
|
32
|
+
if isinstance(value, list):
|
|
33
|
+
return [_stable_report(item) for item in value]
|
|
34
|
+
if isinstance(value, str) and value.startswith("file://"):
|
|
35
|
+
return "file://" + Path(value[7:]).name
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _response(identifier: object, **body: object) -> dict[str, object]:
|
|
40
|
+
return {"jsonrpc": "2.0", "protocol": PROTOCOL, "id": identifier, **body}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _package_transition_natives(natives_to_package: list[tuple], staging: Path, authority: dict[str, str]) -> list[dict[str, object]]:
|
|
44
|
+
"""Repackage installed executables against one complete accepted authority set."""
|
|
45
|
+
return [
|
|
46
|
+
package_native(native, binary, staging, accepted_digest, authority)
|
|
47
|
+
for native, binary, accepted_digest in natives_to_package
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def resolve(params: object) -> dict[str, object]:
|
|
52
|
+
plan, output = parse_request(params)
|
|
53
|
+
checkout = Path(plan["artifact_source"]["checkout_path"])
|
|
54
|
+
projects, source_digest, index_digest = inventory(checkout)
|
|
55
|
+
expected_source = os.environ.get("CF_TRANSITION_SOURCE_DIGEST")
|
|
56
|
+
if expected_source is not None and source_digest != expected_source:
|
|
57
|
+
raise ResolverError("SOURCE_AUTHORITY_DRIFT", "accepted checkout source authority changed")
|
|
58
|
+
return _resolve_v2(params, plan, output, checkout, projects, source_digest, index_digest)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _resolve_v2(params: object, plan: dict[str, object], output: Path, checkout: Path, projects: tuple, source_digest: str, index_digest: str) -> dict[str, object]:
|
|
62
|
+
if not isinstance(params, dict) or params.get("schema") != REQUEST_SCHEMA:
|
|
63
|
+
raise ResolverError("SCHEMA_INVALID", "v2 plan requires v2 resolution request")
|
|
64
|
+
catalog = load_catalog(checkout, str(plan["profile"]))
|
|
65
|
+
phase = params.get("phase") if isinstance(params, dict) else None
|
|
66
|
+
semantic_identity = None
|
|
67
|
+
release_catalog_path = checkout / "stonecastle/cogniflow/src/cogniflow/release-catalog.json"
|
|
68
|
+
# Profile transitions can also request a bootstrap phase from the target
|
|
69
|
+
# interpreter, which deliberately has no public launcher installed. Only
|
|
70
|
+
# the isolated bootstrap host can contribute launcher catalog evidence.
|
|
71
|
+
if phase == "bootstrap" and release_catalog_path.is_file():
|
|
72
|
+
# Bootstrap authority arrives via the launcher's private operation handoff.
|
|
73
|
+
release = operation_catalog(str(plan["profile"]), plan["artifact_source"])
|
|
74
|
+
if release_catalog_path.is_symlink() or release_catalog_path.read_bytes() != release_canonical(release):
|
|
75
|
+
raise ResolverError("SOURCE_AUTHORITY_DRIFT", "checkout catalog differs from launcher authority")
|
|
76
|
+
if release["release_version"] == "0.1.16" and any(
|
|
77
|
+
item["profile"] == plan["profile"] for item in release["profiles"]
|
|
78
|
+
):
|
|
79
|
+
semantic_identity = release_select(release, str(plan["profile"]), target_environment=release_environment())
|
|
80
|
+
if semantic_identity["selection"]["profile_digest"] != "sha256:" + str(catalog["profile"]["sha256"]):
|
|
81
|
+
raise ResolverError("PROFILE_INVALID", "source catalog and indexed TriG profile differ")
|
|
82
|
+
capabilities = tuple(catalog["profile"]["capabilities"])
|
|
83
|
+
verification = catalog["profile"].get("post_install_verification_capability")
|
|
84
|
+
if phase == "post-handoff-verify":
|
|
85
|
+
try:
|
|
86
|
+
return _verify_v2(params, plan, output, checkout, projects, source_digest, index_digest)
|
|
87
|
+
except ResolverError:
|
|
88
|
+
raise
|
|
89
|
+
except Exception as error:
|
|
90
|
+
raise ResolverError("BOUND_BUNDLE_INVALID", "bound bundle validation failed") from error
|
|
91
|
+
natives = tuple(inventory_natives(checkout)) if phase == "bootstrap" else ()
|
|
92
|
+
# These are the executable host, not profile payload. Keeping this list
|
|
93
|
+
# explicit prevents a profile catalog from producing a target that cannot
|
|
94
|
+
# accept handoff or resolve the post-handoff delta.
|
|
95
|
+
host_capabilities = {"urn:cf:service:ProfiledBootstrapInstallationHandoff", "urn:cf:service:ProfileInstallation"}
|
|
96
|
+
if phase == "bootstrap":
|
|
97
|
+
capabilities = tuple(item for item in capabilities if item in host_capabilities)
|
|
98
|
+
elif phase == "post-handoff":
|
|
99
|
+
capabilities = tuple(item for item in capabilities if item not in host_capabilities)
|
|
100
|
+
discovered = discover(checkout, projects, capabilities)
|
|
101
|
+
provider_map = {capability: discovered[capability] for capability in capabilities}
|
|
102
|
+
roots_set = {str(item["distribution"]) for item in provider_map.values()}
|
|
103
|
+
if phase == "bootstrap":
|
|
104
|
+
roots_set.update({"cf-runtime", "cf-service-client", "cf-install-services", "cf-bootstrap-instance", "cf-bootstrap-source-local"})
|
|
105
|
+
roots = tuple(sorted(roots_set))
|
|
106
|
+
selected, external = dependency_closure(projects, roots)
|
|
107
|
+
external = list(external)
|
|
108
|
+
if phase == "post-handoff" and not roots and not selected and not external:
|
|
109
|
+
return _resolve_v2_empty(plan, output, catalog, capabilities, verification, source_digest, index_digest, len(projects))
|
|
110
|
+
external_root = Path(str(params["external_wheelhouse_path"]))
|
|
111
|
+
if output.exists():
|
|
112
|
+
manifest_path = output / "artifact-set.json"
|
|
113
|
+
try:
|
|
114
|
+
raw = manifest_path.read_bytes()
|
|
115
|
+
artifact_set = json.loads(raw)
|
|
116
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
|
|
117
|
+
raise ResolverError("OUTPUT_CONFLICT", "existing v2 artifact output is invalid") from error
|
|
118
|
+
if raw != canonical_json(artifact_set) or not isinstance(artifact_set, dict) or artifact_set.get("schema") != "cf.bootstrap.artifact-set.v2" or artifact_set.get("plan_digest") != "sha256:" + hashlib.sha256(canonical_json(plan)).hexdigest() or artifact_set.get("profile") != plan["profile"]:
|
|
119
|
+
raise ResolverError("OUTPUT_CONFLICT", "existing v2 artifact output is not bound to the requested plan")
|
|
120
|
+
selection = {"profile": artifact_set["profile"], "profile_digest": artifact_set["profile_digest"], "catalog_digest": artifact_set["catalog_digest"], "requested_capabilities": artifact_set["requested_capabilities"], "post_install_verification_capability": artifact_set.get("post_install_verification_capability"), "provider_roots": artifact_set["selected_roots"], "first_party_closure": artifact_set["selected_first_party"], "external_requirements": artifact_set["selected_external"], "target_environment": artifact_set["target_environment"]}
|
|
121
|
+
if artifact_set.get("selection_digest") != "sha256:" + hashlib.sha256(canonical_json(selection)).hexdigest() or not isinstance(artifact_set.get("artifacts"), list):
|
|
122
|
+
raise ResolverError("OUTPUT_CONFLICT", "existing v2 artifact selection is invalid")
|
|
123
|
+
evidence = {"inventory_count": len(projects), "selected_root_count": len(roots), "selected_first_party_count": len(selected), "built_first_party_count": 0, "unselected_build_count": 0, "native_selected": False}
|
|
124
|
+
if semantic_identity is not None:
|
|
125
|
+
evidence["semantic_selection"] = semantic_identity
|
|
126
|
+
return {"schema": RESULT_SCHEMA, "wheelhouse_path": str(output), "artifact_set": artifact_set, "selection": selection, "provider_map": provider_map, "evidence": evidence}
|
|
127
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
staging = Path(tempfile.mkdtemp(prefix="cf-bootstrap-source-local-v2-", dir=str(output.parent)))
|
|
129
|
+
publication = Path(tempfile.mkdtemp(prefix="cf-bootstrap-source-local-v2-publish-", dir=str(output.parent)))
|
|
130
|
+
try:
|
|
131
|
+
cache_root = Path(os.environ["CF_TRANSITION_CACHE_ROOT"]) if os.environ.get("CF_TRANSITION_CACHE_ROOT") and phase == "bootstrap" else None
|
|
132
|
+
cached: list[dict[str, object]] = []
|
|
133
|
+
missing = []
|
|
134
|
+
if cache_root is not None:
|
|
135
|
+
mcp_projects = [native for native in natives if native.normalized_name == "cf-service-mcp-server"]
|
|
136
|
+
if len(mcp_projects) != 1:
|
|
137
|
+
raise ResolverError("NATIVE_AUTHORITY_INVALID", "accepted native authority carrier is not unique")
|
|
138
|
+
mcp = mcp_projects[0]
|
|
139
|
+
carrier_name = f"{mcp.normalized_name.replace('-', '_')}-{mcp.version}-py3-none-{_platform()[1]}.whl"
|
|
140
|
+
carrier = cache_root / carrier_name
|
|
141
|
+
if carrier.is_symlink() or not carrier.is_file():
|
|
142
|
+
raise ResolverError("NATIVE_AUTHORITY_INVALID", "accepted native authority carrier is missing")
|
|
143
|
+
accepted_native_digests = native_authority(carrier, natives)
|
|
144
|
+
for project in selected:
|
|
145
|
+
candidates = sorted(path for path in cache_root.iterdir() if path.is_file() and not path.is_symlink() and path.name.casefold().startswith(project.normalized_name.replace("-", "_") + "-"))
|
|
146
|
+
if len(candidates) > 1:
|
|
147
|
+
raise ResolverError("CACHE_INVALID", "request cache candidate is ambiguous")
|
|
148
|
+
if candidates:
|
|
149
|
+
checked = validate_wheel(candidates[0], project)
|
|
150
|
+
cached.append({"distribution": checked["distribution"], "normalized_distribution": checked["normalized_distribution"], "version": checked["version"], "filename": checked["filename"], "sha256": checked["sha256"], "size": checked["size_bytes"], "source_kind": "stonecastle-local", "source_project": checked["source_project"]})
|
|
151
|
+
shutil.copy2(candidates[0], staging / candidates[0].name)
|
|
152
|
+
else:
|
|
153
|
+
missing.append(project)
|
|
154
|
+
natives_to_build = []
|
|
155
|
+
natives_to_package = []
|
|
156
|
+
for native in natives:
|
|
157
|
+
expected_name = f"{native.normalized_name.replace('-', '_')}-{native.version}-py3-none-" + _platform()[1] + ".whl"
|
|
158
|
+
candidates = sorted(path for path in cache_root.iterdir() if path.is_file() and not path.is_symlink() and path.name.casefold() == expected_name)
|
|
159
|
+
if len(candidates) > 1:
|
|
160
|
+
raise ResolverError("CACHE_INVALID", "request native cache candidate is ambiguous")
|
|
161
|
+
if candidates:
|
|
162
|
+
checked = validate_native_wheel(candidates[0], native)
|
|
163
|
+
if native_executable_sha256(candidates[0], native) != accepted_native_digests[native.normalized_name]:
|
|
164
|
+
raise ResolverError("NATIVE_AUTHORITY_MISMATCH", "cached native executable differs from accepted bootstrap authority")
|
|
165
|
+
cached.append({"distribution": checked["distribution"], "normalized_distribution": checked["normalized_distribution"], "version": checked["version"], "filename": checked["filename"], "sha256": checked["sha256"], "size": checked["size_bytes"], "source_kind": "stonecastle-local", "source_project": checked["source_project"], "artifact_kind": "native", "wheel_tags": checked["wheel_tags"], "executable": checked["executable"]})
|
|
166
|
+
shutil.copy2(candidates[0], staging / candidates[0].name)
|
|
167
|
+
else:
|
|
168
|
+
scripts = Path(str(plan["installation_root"])) / "environment" / ("Scripts" if os.name == "nt" else "bin")
|
|
169
|
+
natives_to_package.append((native, scripts / (native.binary + _platform()[2]), accepted_native_digests[native.normalized_name]))
|
|
170
|
+
else:
|
|
171
|
+
missing = list(selected)
|
|
172
|
+
natives_to_build = list(natives)
|
|
173
|
+
natives_to_package = []
|
|
174
|
+
built = build(tuple(missing), staging, tuple(natives_to_build))
|
|
175
|
+
built.extend(_package_transition_natives(natives_to_package, staging, accepted_native_digests if cache_root is not None else {}))
|
|
176
|
+
|
|
177
|
+
artifacts = cached
|
|
178
|
+
for item in built:
|
|
179
|
+
wheel = staging / "wheels" / str(item["filename"])
|
|
180
|
+
wheel.replace(staging / str(item["filename"]))
|
|
181
|
+
record = {
|
|
182
|
+
"distribution": item["distribution"],
|
|
183
|
+
"normalized_distribution": item["normalized_distribution"],
|
|
184
|
+
"version": item["version"],
|
|
185
|
+
"filename": item["filename"],
|
|
186
|
+
"sha256": item["sha256"],
|
|
187
|
+
"size": item["size_bytes"],
|
|
188
|
+
"source_kind": "stonecastle-local",
|
|
189
|
+
"source_project": item["source_project"],
|
|
190
|
+
}
|
|
191
|
+
if item.get("artifact_kind") == "native":
|
|
192
|
+
record.update({"artifact_kind": "native", "wheel_tags": item["wheel_tags"], "executable": item["executable"]})
|
|
193
|
+
artifacts.append(record)
|
|
194
|
+
grouped_external: dict[str, list[Requirement]] = {}
|
|
195
|
+
for requirement in external:
|
|
196
|
+
try:
|
|
197
|
+
parsed_requirement = Requirement(requirement)
|
|
198
|
+
except Exception as error:
|
|
199
|
+
raise ResolverError("DEPENDENCY_INVALID", f"external requirement is not canonical PEP 508: {requirement}") from error
|
|
200
|
+
grouped_external.setdefault(parsed_requirement.name.replace("_", "-").lower(), []).append(parsed_requirement)
|
|
201
|
+
for normalized, requirements in sorted(grouped_external.items()):
|
|
202
|
+
candidates = sorted(path for path in external_root.iterdir() if path.is_file() and path.suffix == ".whl" and path.name.casefold().startswith(normalized.replace("-", "_") + "-") and not path.name.casefold().startswith("cf-"))
|
|
203
|
+
if len(candidates) != 1:
|
|
204
|
+
raise ResolverError("EXTERNAL_SOURCE_FORBIDDEN", f"no unique external wheel for {normalized}")
|
|
205
|
+
wheel = candidates[0]
|
|
206
|
+
checked = validate_external_wheel(wheel, requirements[0])
|
|
207
|
+
if any(Version(checked["version"]) not in requirement.specifier for requirement in requirements):
|
|
208
|
+
raise ResolverError("ARTIFACT_INVALID", f"external wheel does not satisfy all requirements for {normalized}")
|
|
209
|
+
shutil.copy2(wheel, staging / wheel.name)
|
|
210
|
+
artifacts.append(checked)
|
|
211
|
+
# Include bounded transitive candidates in the offline transaction;
|
|
212
|
+
# the pip report below is the authoritative selected subset.
|
|
213
|
+
from packaging.utils import parse_wheel_filename
|
|
214
|
+
selected_names = {str(item["normalized_distribution"]) for item in artifacts}
|
|
215
|
+
for wheel in sorted(external_root.glob("*.whl"), key=lambda item: item.name.casefold()):
|
|
216
|
+
try:
|
|
217
|
+
distribution, version, _, _ = parse_wheel_filename(wheel.name)
|
|
218
|
+
except Exception as error:
|
|
219
|
+
raise ResolverError("EXTERNAL_SOURCE_FORBIDDEN", "external wheel filename is invalid") from error
|
|
220
|
+
normalized = str(distribution).replace("_", "-").lower()
|
|
221
|
+
if normalized in selected_names or normalized.startswith("cf-"):
|
|
222
|
+
continue
|
|
223
|
+
checked = validate_external_wheel(wheel, Requirement(f"{distribution}=={version}"))
|
|
224
|
+
shutil.copy2(wheel, staging / wheel.name)
|
|
225
|
+
artifacts.append(checked)
|
|
226
|
+
selected_names.add(normalized)
|
|
227
|
+
report_path = staging / "pip-resolver-report.json"
|
|
228
|
+
local_wheels = [str(staging / str(item["filename"])) for item in artifacts if item["source_kind"] == "stonecastle-local"]
|
|
229
|
+
env = os.environ.copy()
|
|
230
|
+
env.update({"PIP_NO_INDEX": "1", "PIP_DISABLE_PIP_VERSION_CHECK": "1", "PIP_CONFIG_FILE": os.devnull, "PYTHONDONTWRITEBYTECODE": "1"})
|
|
231
|
+
env.pop("PYTHONPATH", None)
|
|
232
|
+
report_result = subprocess.run([sys.executable, "-m", "pip", "install", "--dry-run", "--report", str(report_path), "--no-index", "--find-links", str(staging), "--ignore-installed", *local_wheels], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=120, check=False)
|
|
233
|
+
if report_result.returncode != 0:
|
|
234
|
+
raise ResolverError("EXTERNAL_RESOLUTION_FAILED", "offline pip resolver report failed")
|
|
235
|
+
report = json.loads(report_path.read_text(encoding="utf-8"))
|
|
236
|
+
if not isinstance(report, dict) or not isinstance(report.get("install"), list):
|
|
237
|
+
raise ResolverError("EXTERNAL_RESOLUTION_FAILED", "pip resolver report is invalid")
|
|
238
|
+
report_names = {str(item.get("metadata", {}).get("name", "")).casefold().replace("_", "-") for item in report["install"] if isinstance(item, dict)}
|
|
239
|
+
expected_names = {str(item["normalized_distribution"]) for item in artifacts}
|
|
240
|
+
if not report_names <= expected_names:
|
|
241
|
+
raise ResolverError("SELECTION_INVALID", "pip resolver selected an unexpected distribution")
|
|
242
|
+
artifacts = [item for item in artifacts if str(item["normalized_distribution"]) in report_names]
|
|
243
|
+
external = [f"{item['distribution']}=={item['version']}" for item in artifacts if item["source_kind"] == "external-wheelhouse"]
|
|
244
|
+
report_digest = hashlib.sha256(canonical_json(_stable_report(report))).hexdigest()
|
|
245
|
+
report_path.unlink()
|
|
246
|
+
profile_digest = "sha256:" + str(catalog["profile"]["sha256"])
|
|
247
|
+
target_environment = {"implementation_name": "cpython", "python_full_version": platform.python_version(), "python_version": platform.python_version(), "sys_platform": sys.platform, "platform_system": platform.system(), "platform_machine": platform.machine().lower(), "platform_python_implementation": platform.python_implementation()}
|
|
248
|
+
selection = {"profile": plan["profile"], "profile_digest": profile_digest, "catalog_digest": catalog["catalog_digest"], "requested_capabilities": list(capabilities), "post_install_verification_capability": verification, "provider_roots": list(roots), "first_party_closure": [item.normalized_name for item in selected], "external_requirements": list(external), "target_environment": target_environment}
|
|
249
|
+
selection_digest = "sha256:" + hashlib.sha256(canonical_json(selection)).hexdigest()
|
|
250
|
+
artifact_set = {"schema": "cf.bootstrap.artifact-set.v2", "source_mode": "local-repository", "plan_digest": "sha256:" + hashlib.sha256(canonical_json(plan)).hexdigest(), "profile": plan["profile"], "profile_digest": profile_digest, "catalog_digest": catalog["catalog_digest"], "requested_capabilities": list(capabilities), "post_install_verification_capability": verification, "selected_roots": list(roots), "selected_first_party": [item.normalized_name for item in selected], "selected_external": list(external), "selection_digest": selection_digest, "repository_source_digest": source_digest, "selected_source_digest": "sha256:" + hashlib.sha256(canonical_json([item.relative for item in selected])).hexdigest(), "index_digest": index_digest, "resolver": {"mode": "pip-dry-run-report", "pip_no_index": True, "report_sha256": report_digest}, "target_environment": selection["target_environment"], "artifacts": sorted(artifacts, key=lambda item: (item["normalized_distribution"], item["filename"]))}
|
|
251
|
+
# Keep all build homes, caches, reports, and source copies out of the
|
|
252
|
+
# immutable bundle. Only the verified selected wheels are published.
|
|
253
|
+
for item in artifact_set["artifacts"]:
|
|
254
|
+
source = staging / str(item["filename"])
|
|
255
|
+
if not source.is_file() or source.is_symlink():
|
|
256
|
+
raise ResolverError("ARTIFACT_INVALID", "selected artifact is not a regular file")
|
|
257
|
+
shutil.copy2(source, publication / str(item["filename"]))
|
|
258
|
+
(publication / "artifact-set.json").write_bytes(canonical_json(artifact_set))
|
|
259
|
+
expected = {"artifact-set.json", *(str(item["filename"]) for item in artifact_set["artifacts"])}
|
|
260
|
+
actual = {item.name for item in publication.iterdir()}
|
|
261
|
+
if actual != expected or any(not item.is_file() or item.is_symlink() for item in publication.iterdir()):
|
|
262
|
+
raise ResolverError("BUNDLE_CONTENT_INVALID", "published bundle inventory is not canonical")
|
|
263
|
+
publication.rename(output)
|
|
264
|
+
shutil.rmtree(staging, ignore_errors=True)
|
|
265
|
+
except ResolverError:
|
|
266
|
+
shutil.rmtree(staging, ignore_errors=True)
|
|
267
|
+
shutil.rmtree(publication, ignore_errors=True)
|
|
268
|
+
raise
|
|
269
|
+
except (OSError, RuntimeError) as error:
|
|
270
|
+
shutil.rmtree(staging, ignore_errors=True)
|
|
271
|
+
shutil.rmtree(publication, ignore_errors=True)
|
|
272
|
+
raise ResolverError("INTERNAL_ERROR", "v2 artifact publication failed") from error
|
|
273
|
+
evidence = {"inventory_count": len(projects), "selected_root_count": len(roots), "selected_first_party_count": len(selected), "built_first_party_count": len(selected), "unselected_build_count": 0, "native_selected": False}
|
|
274
|
+
if semantic_identity is not None:
|
|
275
|
+
evidence["semantic_selection"] = semantic_identity
|
|
276
|
+
return {"schema": RESULT_SCHEMA, "wheelhouse_path": str(output), "artifact_set": artifact_set, "selection": selection, "provider_map": provider_map, "evidence": evidence}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _resolve_v2_empty(plan: dict[str, object], output: Path, catalog: dict[str, object], capabilities: tuple, verification: object, source_digest: str, index_digest: str, inventory_count: int) -> dict[str, object]:
|
|
280
|
+
"""Publish the canonical bundle for a profile with no post-handoff payload."""
|
|
281
|
+
profile_digest = "sha256:" + str(catalog["profile"]["sha256"])
|
|
282
|
+
target_environment = {"implementation_name": "cpython", "python_full_version": platform.python_version(), "python_version": platform.python_version(), "sys_platform": sys.platform, "platform_system": platform.system(), "platform_machine": platform.machine().lower(), "platform_python_implementation": platform.python_implementation()}
|
|
283
|
+
selection = {"profile": plan["profile"], "profile_digest": profile_digest, "catalog_digest": catalog["catalog_digest"], "requested_capabilities": list(capabilities), "post_install_verification_capability": verification, "provider_roots": [], "first_party_closure": [], "external_requirements": [], "target_environment": target_environment}
|
|
284
|
+
selection_digest = "sha256:" + hashlib.sha256(canonical_json(selection)).hexdigest()
|
|
285
|
+
artifact_set = {"schema": "cf.bootstrap.artifact-set.v2", "source_mode": "local-repository", "plan_digest": "sha256:" + hashlib.sha256(canonical_json(plan)).hexdigest(), "profile": plan["profile"], "profile_digest": profile_digest, "catalog_digest": catalog["catalog_digest"], "requested_capabilities": list(capabilities), "post_install_verification_capability": verification, "selected_roots": [], "selected_first_party": [], "selected_external": [], "selection_digest": selection_digest, "repository_source_digest": source_digest, "selected_source_digest": "sha256:" + hashlib.sha256(canonical_json([])).hexdigest(), "index_digest": index_digest, "resolver": {"mode": "empty-closure", "pip_no_index": True, "report_sha256": hashlib.sha256(canonical_json({"install": []})).hexdigest()}, "target_environment": target_environment, "artifacts": []}
|
|
286
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
287
|
+
if output.exists():
|
|
288
|
+
manifest_path = output / "artifact-set.json"
|
|
289
|
+
try:
|
|
290
|
+
raw = manifest_path.read_bytes()
|
|
291
|
+
existing = json.loads(raw)
|
|
292
|
+
entries = list(output.iterdir())
|
|
293
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
|
|
294
|
+
raise ResolverError("OUTPUT_CONFLICT", "existing empty v2 artifact output is invalid") from error
|
|
295
|
+
if raw != canonical_json(existing) or existing != artifact_set or len(entries) != 1 or entries[0] != manifest_path or not manifest_path.is_file() or manifest_path.is_symlink():
|
|
296
|
+
raise ResolverError("OUTPUT_CONFLICT", "existing empty v2 artifact output is not canonical")
|
|
297
|
+
return {"schema": RESULT_SCHEMA, "wheelhouse_path": str(output), "artifact_set": artifact_set, "selection": selection, "provider_map": {}, "evidence": {"inventory_count": inventory_count, "selected_root_count": 0, "selected_first_party_count": 0, "built_first_party_count": 0, "unselected_build_count": 0, "native_selected": False}}
|
|
298
|
+
publication = Path(tempfile.mkdtemp(prefix="cf-bootstrap-source-local-v2-empty-", dir=str(output.parent)))
|
|
299
|
+
try:
|
|
300
|
+
(publication / "artifact-set.json").write_bytes(canonical_json(artifact_set))
|
|
301
|
+
publication.rename(output)
|
|
302
|
+
except (OSError, RuntimeError) as error:
|
|
303
|
+
shutil.rmtree(publication, ignore_errors=True)
|
|
304
|
+
raise ResolverError("INTERNAL_ERROR", "v2 artifact publication failed") from error
|
|
305
|
+
return {"schema": RESULT_SCHEMA, "wheelhouse_path": str(output), "artifact_set": artifact_set, "selection": selection, "provider_map": {}, "evidence": {"inventory_count": inventory_count, "selected_root_count": 0, "selected_first_party_count": 0, "built_first_party_count": 0, "unselected_build_count": 0, "native_selected": False}}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _verify_external_selection(bound: object, indexed: tuple, entries: list) -> None:
|
|
309
|
+
"""Prove exact bound pins are equivalent to the indexed constraints."""
|
|
310
|
+
if not isinstance(bound, list) or any(not isinstance(item, str) for item in bound):
|
|
311
|
+
raise ResolverError("SELECTION_MISMATCH", "bound external selection is invalid")
|
|
312
|
+
requirements: dict[str, list[Requirement]] = {}
|
|
313
|
+
try:
|
|
314
|
+
for value in indexed:
|
|
315
|
+
requirement = Requirement(value)
|
|
316
|
+
normalized = requirement.name.replace("_", "-").lower()
|
|
317
|
+
requirements.setdefault(normalized, []).append(requirement)
|
|
318
|
+
except Exception as error:
|
|
319
|
+
raise ResolverError("SELECTION_MISMATCH", "indexed external selection is invalid") from error
|
|
320
|
+
|
|
321
|
+
pins: dict[str, tuple[Requirement, Version]] = {}
|
|
322
|
+
try:
|
|
323
|
+
for value in bound:
|
|
324
|
+
requirement = Requirement(value)
|
|
325
|
+
specifiers = list(requirement.specifier)
|
|
326
|
+
normalized = requirement.name.replace("_", "-").lower()
|
|
327
|
+
if str(requirement) != value or requirement.extras or requirement.marker is not None or requirement.url is not None or len(specifiers) != 1 or specifiers[0].operator != "==" or normalized in pins:
|
|
328
|
+
raise ValueError
|
|
329
|
+
pins[normalized] = (requirement, Version(specifiers[0].version))
|
|
330
|
+
except (InvalidVersion, ValueError) as error:
|
|
331
|
+
raise ResolverError("SELECTION_MISMATCH", "bound external selection is not canonical exact pins") from error
|
|
332
|
+
if set(pins) != set(requirements):
|
|
333
|
+
raise ResolverError("SELECTION_MISMATCH", "bound external selection is incomplete")
|
|
334
|
+
|
|
335
|
+
external_entries: dict[str, dict] = {}
|
|
336
|
+
for item in entries:
|
|
337
|
+
if isinstance(item, dict) and item.get("source_kind") == "external-wheelhouse":
|
|
338
|
+
normalized = item.get("normalized_distribution")
|
|
339
|
+
if not isinstance(normalized, str):
|
|
340
|
+
raise ResolverError("SELECTION_MISMATCH", "bound external artifact selection is invalid")
|
|
341
|
+
normalized = normalized.replace("_", "-").lower()
|
|
342
|
+
if normalized in external_entries:
|
|
343
|
+
raise ResolverError("SELECTION_MISMATCH", "bound external artifact selection is not unique")
|
|
344
|
+
external_entries[normalized] = item
|
|
345
|
+
if set(external_entries) != set(requirements):
|
|
346
|
+
raise ResolverError("SELECTION_MISMATCH", "bound external artifacts do not match the indexed selection")
|
|
347
|
+
|
|
348
|
+
for normalized, constraints in requirements.items():
|
|
349
|
+
requirement, version = pins[normalized]
|
|
350
|
+
entry = external_entries[normalized]
|
|
351
|
+
if requirement.name.replace("_", "-").lower() != normalized or str(entry.get("normalized_distribution")).replace("_", "-").lower() != normalized or str(entry.get("version")) != str(version) or any(version not in constraint.specifier for constraint in constraints):
|
|
352
|
+
raise ResolverError("SELECTION_MISMATCH", "bound external pin does not satisfy the indexed selection")
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _verify_v2(params: object, plan: dict[str, object], output: Path, checkout: Path, projects: tuple, source_digest: str, index_digest: str) -> dict[str, object]:
|
|
356
|
+
"""Replay an already published bundle without invoking a build tool."""
|
|
357
|
+
if not _link_free(output) or not output.is_dir() or output.is_symlink():
|
|
358
|
+
raise ResolverError("BOUND_BUNDLE_MISSING", "bound post-handoff bundle is missing")
|
|
359
|
+
manifest_path = output / "artifact-set.json"
|
|
360
|
+
try:
|
|
361
|
+
raw = manifest_path.read_bytes()
|
|
362
|
+
artifact_set = json.loads(raw)
|
|
363
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
|
|
364
|
+
raise ResolverError("BOUND_BUNDLE_INVALID", "bound artifact manifest is invalid") from error
|
|
365
|
+
if raw != canonical_json(artifact_set) or not isinstance(artifact_set, dict):
|
|
366
|
+
raise ResolverError("BOUND_BUNDLE_INVALID", "bound artifact manifest is not canonical")
|
|
367
|
+
if artifact_set.get("schema") != "cf.bootstrap.artifact-set.v2" or artifact_set.get("plan_digest") != "sha256:" + hashlib.sha256(canonical_json(plan)).hexdigest():
|
|
368
|
+
raise ResolverError("BOUND_BUNDLE_INVALID", "bound artifact manifest is not plan-bound")
|
|
369
|
+
if artifact_set.get("repository_source_digest") != source_digest or artifact_set.get("index_digest") != index_digest:
|
|
370
|
+
raise ResolverError("BOUND_BUNDLE_INVALID", "bound source or index identity changed")
|
|
371
|
+
expected_bindings = {"bound_artifact_set_digest": "sha256:" + hashlib.sha256(canonical_json(artifact_set)).hexdigest(), "bound_selection_digest": artifact_set.get("selection_digest"), "bound_profile_digest": artifact_set.get("profile_digest"), "bound_catalog_digest": artifact_set.get("catalog_digest"), "bound_repository_source_digest": artifact_set.get("repository_source_digest"), "bound_index_digest": artifact_set.get("index_digest")}
|
|
372
|
+
if any(params.get(key) != value for key, value in expected_bindings.items()):
|
|
373
|
+
raise ResolverError("BOUND_BUNDLE_INVALID", "bound artifact identity does not match the receipt")
|
|
374
|
+
catalog = load_catalog(checkout, str(plan["profile"]))
|
|
375
|
+
host_capabilities = {"urn:cf:service:ProfiledBootstrapInstallationHandoff", "urn:cf:service:ProfileInstallation"}
|
|
376
|
+
capabilities = tuple(item for item in catalog["profile"]["capabilities"] if item not in host_capabilities)
|
|
377
|
+
discovered = discover(checkout, projects, capabilities)
|
|
378
|
+
provider_map = {capability: discovered[capability] for capability in capabilities}
|
|
379
|
+
roots = tuple(sorted({str(item["distribution"]) for item in provider_map.values()}))
|
|
380
|
+
selected, external = dependency_closure(projects, roots)
|
|
381
|
+
expected_selection = {
|
|
382
|
+
"profile": plan["profile"],
|
|
383
|
+
"profile_digest": "sha256:" + str(catalog["profile"]["sha256"]),
|
|
384
|
+
"catalog_digest": catalog["catalog_digest"],
|
|
385
|
+
"requested_capabilities": list(capabilities),
|
|
386
|
+
"post_install_verification_capability": catalog["profile"].get("post_install_verification_capability"),
|
|
387
|
+
"provider_roots": list(roots),
|
|
388
|
+
"first_party_closure": [item.normalized_name for item in selected],
|
|
389
|
+
"external_requirements": artifact_set.get("selected_external"),
|
|
390
|
+
"target_environment": artifact_set.get("target_environment"),
|
|
391
|
+
}
|
|
392
|
+
selection = {"profile": artifact_set.get("profile"), "profile_digest": artifact_set.get("profile_digest"), "catalog_digest": artifact_set.get("catalog_digest"), "requested_capabilities": artifact_set.get("requested_capabilities"), "post_install_verification_capability": artifact_set.get("post_install_verification_capability"), "provider_roots": artifact_set.get("selected_roots"), "first_party_closure": artifact_set.get("selected_first_party"), "external_requirements": artifact_set.get("selected_external"), "target_environment": artifact_set.get("target_environment")}
|
|
393
|
+
if selection != expected_selection:
|
|
394
|
+
raise ResolverError("SELECTION_MISMATCH", "bound selection does not match the indexed profile")
|
|
395
|
+
if artifact_set.get("selection_digest") != "sha256:" + hashlib.sha256(canonical_json(selection)).hexdigest():
|
|
396
|
+
raise ResolverError("BOUND_BUNDLE_INVALID", "bound selection digest is invalid")
|
|
397
|
+
entries = artifact_set.get("artifacts")
|
|
398
|
+
if not isinstance(entries, list) or (not entries and (selection["provider_roots"] or selection["first_party_closure"] or selection["external_requirements"])):
|
|
399
|
+
raise ResolverError("BOUND_BUNDLE_INVALID", "bound artifact list is invalid")
|
|
400
|
+
_verify_external_selection(selection["external_requirements"], external, entries)
|
|
401
|
+
expected_files = {"artifact-set.json"}
|
|
402
|
+
for item in entries:
|
|
403
|
+
try:
|
|
404
|
+
from packaging.utils import parse_wheel_filename
|
|
405
|
+
allowed = {"distribution", "normalized_distribution", "version", "filename", "sha256", "size", "source_kind"}
|
|
406
|
+
if isinstance(item, dict) and item.get("source_kind") == "stonecastle-local":
|
|
407
|
+
allowed.add("source_project")
|
|
408
|
+
if isinstance(item, dict) and item.get("artifact_kind") == "native":
|
|
409
|
+
allowed |= {"artifact_kind", "wheel_tags", "executable"}
|
|
410
|
+
if not isinstance(item, dict) or set(item) - allowed or not isinstance(item.get("filename"), str):
|
|
411
|
+
raise ResolverError("ARTIFACT_ENTRY_INVALID", "bound artifact entry is invalid")
|
|
412
|
+
required = {"distribution", "normalized_distribution", "version", "filename", "sha256", "size", "source_kind"}
|
|
413
|
+
if isinstance(item, dict) and item.get("source_kind") == "stonecastle-local":
|
|
414
|
+
required.add("source_project")
|
|
415
|
+
if not required.issubset(item):
|
|
416
|
+
raise ResolverError("ARTIFACT_ENTRY_INVALID", "bound artifact entry fields are incomplete")
|
|
417
|
+
distribution, filename_version, _, filename_tags = parse_wheel_filename(item["filename"])
|
|
418
|
+
if str(distribution).replace("_", "-").lower() != str(item["normalized_distribution"]).lower() or str(filename_version) != str(item["version"]):
|
|
419
|
+
raise ResolverError("ARTIFACT_METADATA_MISMATCH", "wheel filename does not match its artifact entry")
|
|
420
|
+
if item.get("source_kind") == "stonecastle-local":
|
|
421
|
+
if not any(project.normalized_name == str(item["normalized_distribution"]).lower() and project.relative == item["source_project"] for project in projects):
|
|
422
|
+
raise ResolverError("ARTIFACT_PROVENANCE_INVALID", "first-party artifact provenance is invalid")
|
|
423
|
+
elif item.get("source_kind") != "external-wheelhouse":
|
|
424
|
+
raise ResolverError("ARTIFACT_PROVENANCE_INVALID", "artifact source kind is invalid")
|
|
425
|
+
filename = item["filename"]
|
|
426
|
+
wheel = output / filename
|
|
427
|
+
if wheel.is_symlink() or not wheel.is_file() or filename in expected_files or not _link_free(wheel) or wheel.resolve().parent != output.resolve():
|
|
428
|
+
raise ResolverError("WHEEL_MISSING_OR_UNSAFE", "bound wheel is missing or unsafe")
|
|
429
|
+
if wheel.stat().st_size != item.get("size") or hashlib.sha256(wheel.read_bytes()).hexdigest() != item.get("sha256"):
|
|
430
|
+
raise ResolverError("WHEEL_DIGEST_MISMATCH", "bound wheel bytes changed")
|
|
431
|
+
import zipfile
|
|
432
|
+
except ResolverError:
|
|
433
|
+
raise
|
|
434
|
+
except (OSError, TypeError, KeyError) as error:
|
|
435
|
+
raise ResolverError("ARTIFACT_ENTRY_INVALID", "bound artifact entry cannot be validated") from error
|
|
436
|
+
try:
|
|
437
|
+
with zipfile.ZipFile(wheel) as archive:
|
|
438
|
+
archive_names = archive.namelist()
|
|
439
|
+
if len(archive_names) != len(set(archive_names)) or len({name.casefold() for name in archive_names}) != len(archive_names) or any(not _safe_zip_name(name.rstrip("/")) for name in archive_names):
|
|
440
|
+
raise ValueError
|
|
441
|
+
import stat
|
|
442
|
+
for info in archive.infolist():
|
|
443
|
+
mode = (info.external_attr >> 16) & 0o170000
|
|
444
|
+
if stat.S_ISLNK(mode) or stat.S_ISCHR(mode) or stat.S_ISBLK(mode) or stat.S_ISFIFO(mode) or (stat.S_ISDIR(mode) and not info.filename.endswith("/")):
|
|
445
|
+
raise ValueError
|
|
446
|
+
records = [name for name in archive_names if name.endswith(".dist-info/RECORD")]
|
|
447
|
+
if len(records) != 1:
|
|
448
|
+
raise ValueError
|
|
449
|
+
_validate_record(archive, [name for name in archive_names if not name.endswith("/")], records[0])
|
|
450
|
+
metadata_names = [name for name in archive_names if name.endswith(".dist-info/METADATA")]
|
|
451
|
+
wheel_names = [name for name in archive_names if name.endswith(".dist-info/WHEEL")]
|
|
452
|
+
if len(metadata_names) != 1 or len(wheel_names) != 1:
|
|
453
|
+
raise ValueError
|
|
454
|
+
metadata = archive.read(metadata_names[0]).decode("utf-8")
|
|
455
|
+
fields = dict(line.split(": ", 1) for line in metadata.splitlines() if ": " in line and line.split(": ", 1)[0] in {"Name", "Version"})
|
|
456
|
+
tags = {line.split(":", 1)[1].strip() for line in archive.read(wheel_names[0]).decode("utf-8").splitlines() if line.startswith("Tag:")}
|
|
457
|
+
if fields.get("Name", "").replace("_", "-").lower() != str(item["normalized_distribution"]).lower() or fields.get("Version") != str(item["version"]) or tags != {str(tag) for tag in filename_tags}:
|
|
458
|
+
raise ValueError
|
|
459
|
+
except (OSError, ValueError, zipfile.BadZipFile, UnicodeDecodeError) as error:
|
|
460
|
+
raise ResolverError("WHEEL_METADATA_INVALID", "bound wheel metadata or RECORD is invalid") from error
|
|
461
|
+
expected_files.add(wheel.name)
|
|
462
|
+
actual = {item.name for item in output.iterdir() if item.is_file()}
|
|
463
|
+
if actual != expected_files or any(item.is_dir() or item.is_symlink() for item in output.iterdir()):
|
|
464
|
+
raise ResolverError("BUNDLE_CONTENT_INVALID", "bound bundle contains unexpected entries")
|
|
465
|
+
return {"schema": RESULT_SCHEMA, "wheelhouse_path": str(output), "artifact_set": artifact_set, "selection": selection, "provider_map": provider_map, "evidence": {"verify_only": True, "unselected_build_count": 0, "native_selected": False}}
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _link_free(path: Path) -> bool:
|
|
469
|
+
"""Check every existing component, including Windows reparse points."""
|
|
470
|
+
import stat
|
|
471
|
+
|
|
472
|
+
current = path
|
|
473
|
+
components = []
|
|
474
|
+
while current != Path(current.anchor):
|
|
475
|
+
components.append(current)
|
|
476
|
+
current = current.parent
|
|
477
|
+
for component in reversed(components):
|
|
478
|
+
if component.is_symlink():
|
|
479
|
+
return False
|
|
480
|
+
if os.name == "nt" and component.exists():
|
|
481
|
+
try:
|
|
482
|
+
if os.lstat(component).st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT:
|
|
483
|
+
return False
|
|
484
|
+
except OSError:
|
|
485
|
+
return False
|
|
486
|
+
return True
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def validate_external_wheel(path: Path, requirement: Requirement) -> dict[str, object]:
|
|
490
|
+
"""Validate the bounded external source without importing its code."""
|
|
491
|
+
import stat
|
|
492
|
+
import zipfile
|
|
493
|
+
import re
|
|
494
|
+
from packaging.tags import sys_tags
|
|
495
|
+
from packaging.utils import parse_wheel_filename
|
|
496
|
+
raw = path.read_bytes()
|
|
497
|
+
if path.is_symlink() or not path.is_file() or path.suffix != ".whl" or path.name.casefold().startswith("cf-"):
|
|
498
|
+
raise ResolverError("EXTERNAL_SOURCE_FORBIDDEN", "external wheel is unsafe")
|
|
499
|
+
try:
|
|
500
|
+
end = raw.rfind(b"PK\x05\x06")
|
|
501
|
+
if end < 0 or end + 22 > len(raw) or end + 22 + int.from_bytes(raw[end + 20:end + 22], "little") != len(raw):
|
|
502
|
+
raise ValueError("malformed ZIP end record")
|
|
503
|
+
with zipfile.ZipFile(path) as archive:
|
|
504
|
+
names = archive.namelist()
|
|
505
|
+
if len(names) != len(set(names)) or len({name.casefold() for name in names}) != len(names) or any(not _safe_zip_name(name.rstrip("/")) for name in names):
|
|
506
|
+
raise ValueError("unsafe archive names")
|
|
507
|
+
payload_names = [name for name in names if not name.endswith("/")]
|
|
508
|
+
metadata_names = [name for name in archive.namelist() if name.endswith(".dist-info/METADATA")]
|
|
509
|
+
wheel_names = [name for name in archive.namelist() if name.endswith(".dist-info/WHEEL")]
|
|
510
|
+
record_names = [name for name in archive.namelist() if name.endswith(".dist-info/RECORD")]
|
|
511
|
+
if len(metadata_names) != 1 or len(wheel_names) != 1 or len(record_names) != 1:
|
|
512
|
+
raise ValueError
|
|
513
|
+
for info in archive.infolist():
|
|
514
|
+
mode = (info.external_attr >> 16) & 0o170000
|
|
515
|
+
if stat.S_ISLNK(mode) or stat.S_ISCHR(mode) or stat.S_ISBLK(mode) or stat.S_ISFIFO(mode) or (stat.S_ISDIR(mode) and not info.filename.endswith("/")):
|
|
516
|
+
raise ValueError("unsafe archive entry")
|
|
517
|
+
_validate_record(archive, payload_names, record_names[0])
|
|
518
|
+
metadata = archive.read(metadata_names[0]).decode("utf-8")
|
|
519
|
+
wheel = archive.read(wheel_names[0]).decode("utf-8")
|
|
520
|
+
fields = dict(re.findall(r"^(Name|Version): (.+)$", metadata, re.MULTILINE))
|
|
521
|
+
_, filename_version, _, filename_tags = parse_wheel_filename(path.name)
|
|
522
|
+
wheel_tags = {line.split(":", 1)[1].strip() for line in wheel.splitlines() if line.startswith("Tag:")}
|
|
523
|
+
compatible = {str(tag) for tag in sys_tags()}
|
|
524
|
+
except (OSError, ValueError, zipfile.BadZipFile, UnicodeDecodeError) as error:
|
|
525
|
+
raise ResolverError("ARTIFACT_INVALID", "external wheel metadata is invalid") from error
|
|
526
|
+
normalized = re.sub(r"[-_.]+", "-", fields.get("Name", "")).lower()
|
|
527
|
+
try:
|
|
528
|
+
version = Version(fields.get("Version", ""))
|
|
529
|
+
except InvalidVersion as error:
|
|
530
|
+
raise ResolverError("ARTIFACT_INVALID", "external wheel version is invalid") from error
|
|
531
|
+
if normalized != requirement.name.replace("_", "-").lower() or not fields.get("Version") or version not in requirement.specifier or filename_version != version or {str(tag) for tag in filename_tags} != wheel_tags or not wheel_tags or not (wheel_tags & compatible) or any(name.endswith((".pth", "direct_url.json")) for name in names) or "Editable-Project-Location:" in metadata:
|
|
532
|
+
raise ResolverError("ARTIFACT_INVALID", "external wheel does not match requirement")
|
|
533
|
+
return {"distribution": fields["Name"], "normalized_distribution": normalized, "version": fields["Version"], "filename": path.name, "sha256": hashlib.sha256(raw).hexdigest(), "size": path.stat().st_size, "source_kind": "external-wheelhouse"}
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def handle(payload: object) -> dict[str, object]:
|
|
537
|
+
identifier = payload.get("id") if isinstance(payload, dict) else None
|
|
538
|
+
try:
|
|
539
|
+
if not isinstance(payload, dict):
|
|
540
|
+
raise ResolverError("INVALID_ENVELOPE", "request envelope must be an object")
|
|
541
|
+
unknown = set(payload) - _FIELDS
|
|
542
|
+
missing = _FIELDS - set(payload)
|
|
543
|
+
if unknown:
|
|
544
|
+
raise ResolverError("UNKNOWN_ENVELOPE_FIELD", "request envelope fields are invalid")
|
|
545
|
+
if missing:
|
|
546
|
+
raise ResolverError("INVALID_ENVELOPE", "request envelope is missing required fields")
|
|
547
|
+
if payload.get("jsonrpc") != "2.0" or not isinstance(payload.get("id"), (str, int)) or isinstance(payload.get("id"), bool):
|
|
548
|
+
raise ResolverError("INVALID_ENVELOPE", "request envelope identity is invalid")
|
|
549
|
+
if payload.get("protocol") != PROTOCOL:
|
|
550
|
+
raise ResolverError("PROTOCOL_VERSION_UNSUPPORTED", "unsupported resolver protocol version")
|
|
551
|
+
if payload.get("method") != "artifacts/resolve":
|
|
552
|
+
raise ResolverError("UNKNOWN_OPERATION", "unknown resolver operation")
|
|
553
|
+
return _response(identifier, result=resolve(payload.get("params")))
|
|
554
|
+
except ResolverError as error:
|
|
555
|
+
return _response(identifier, error={"code": error.code, "message": str(error)})
|
|
556
|
+
except Exception as error:
|
|
557
|
+
return _response(identifier, error={"code": "INTERNAL_ERROR", "message": f"resolver failed unexpectedly: {error}"})
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def main() -> int:
|
|
561
|
+
for line in sys.stdin:
|
|
562
|
+
try:
|
|
563
|
+
payload = json.loads(line)
|
|
564
|
+
response = handle(payload)
|
|
565
|
+
except json.JSONDecodeError:
|
|
566
|
+
response = _response(None, error={"code": "MALFORMED_JSON", "message": "request must be valid JSON"})
|
|
567
|
+
sys.stdout.write(canonical_json(response).decode())
|
|
568
|
+
sys.stdout.flush()
|
|
569
|
+
return 0
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
if __name__ == "__main__":
|
|
573
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@prefix cfpkg: <https://cogniflow.odea-project.org/cf#> .
|
|
2
|
+
@prefix contribution: <urn:cf:contribution:> .
|
|
3
|
+
@prefix pkg: <urn:cf:pkg:> .
|
|
4
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
5
|
+
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
|
|
6
|
+
|
|
7
|
+
pkg:cf_bootstrap_source_local {
|
|
8
|
+
pkg:cf_bootstrap_source_local a cfpkg:CfPackage ;
|
|
9
|
+
cfpkg:hasPackageManifest [ a cfpkg:PackageManifest ; cfpkg:hasDistributionName [ a cfpkg:DistributionName ; rdf:value "cf-bootstrap-source-local" ] ; cfpkg:hasPythonPackageName [ a cfpkg:PythonPackageName ; rdf:value "cf_bootstrap_source_local" ] ; cfpkg:hasPackageVersion [ a cfpkg:PackageVersion ; rdf:value "0.1.0" ] ; cfpkg:hasImplementationLanguage [ a cfpkg:ImplementationLanguage ; rdf:value "Python" ] ] ;
|
|
10
|
+
cfpkg:hasPackageRole [ a cfpkg:PackageRole ; rdf:value cfpkg:SpecificationPackageRole ] ;
|
|
11
|
+
cfpkg:hasPackageContribution contribution:adopted_package ;
|
|
12
|
+
skos:prefLabel "cf-bootstrap-source-local" ; skos:definition "An adopted Cogniflow package conforming to the basic package template." ; skos:scopeNote "Package semantics were restored through the package conformance repair service." ; skos:example "The installed package publishes its package.trig semantic source." .
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cf-bootstrap-source-local
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Offline deterministic Stonecastle local repository artifact resolver.
|
|
5
|
+
Author: Cogniflow Maintainers
|
|
6
|
+
License: GPL-3
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: cf-bootstrap-source-pypi==0.1.16
|
|
9
|
+
Requires-Dist: packaging<27,>=24
|
|
10
|
+
Requires-Dist: rdflib<8,>=7
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# Local repository artifact resolver
|
|
14
|
+
|
|
15
|
+
`cf-bootstrap-source-local` converts a Stonecastle source checkout into a verified, offline installation bundle. It is the artifact-source implementation used by Local Repository Mode.
|
|
16
|
+
|
|
17
|
+
## Installation role
|
|
18
|
+
|
|
19
|
+
The resolver validates the direct first-party project inventory and semantic-navigation index, loads the selected installation profile, reads its canonical `hasCapabilityRequest` relations from local TriG, and discovers matching service operations from indexed service semantics. This discovery is deliberately local because it runs before Fuseki or the production semantic graph exists.
|
|
20
|
+
|
|
21
|
+
For the selected providers it computes the first-party dependency closure, validates bounded external wheels, builds non-editable wheels, packages declared native artifacts, and publishes an immutable `artifact-set.json` with source, index, profile, catalog, selection, artifact, and native-authority digests. Bootstrap and post-handoff phases are resolved separately so only required components enter each transaction.
|
|
22
|
+
|
|
23
|
+
## Protocol and boundaries
|
|
24
|
+
|
|
25
|
+
The console script implements `cf.bootstrap.artifact-resolver.v1` as one-request/one-response JSON lines. Principal inputs are a canonical bootstrap plan, phase, checkout path, artifact output root, and explicit external wheelhouse. Outputs are a canonical artifact-resolution result, provider map, selection evidence, and wheelhouse.
|
|
26
|
+
|
|
27
|
+
The resolver never installs into the target, creates the instance environment, starts services, contacts a SPARQL endpoint, or mutates the checkout. Network access is not used for first-party resolution; external dependencies must already be present in the bounded wheelhouse. Target application belongs to `cf-bootstrap-instance`, while orchestration belongs to `cf-bootstrap-orchestrator`.
|