cf-bootstrap-core 0.1.8__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.
@@ -0,0 +1,35 @@
1
+ """Pure bootstrap planning primitives."""
2
+
3
+ from pathlib import Path
4
+
5
+ PACKAGE_DISTRIBUTION = "cf-bootstrap-core"
6
+ PYTHON_PACKAGE = "cf_bootstrap_core"
7
+ PACKAGE_VERSION = "0.1.8"
8
+
9
+
10
+ def package_root() -> Path:
11
+ return Path(__file__).resolve().parent
12
+
13
+
14
+ def semantics_dir() -> Path:
15
+ return package_root() / "semantics"
16
+
17
+
18
+ def semantic_files() -> tuple[Path, ...]:
19
+ return (semantics_dir() / "package.trig",)
20
+
21
+ from .model import (
22
+ ArtifactSource,
23
+ BootstrapPlan,
24
+ BootstrapRequest,
25
+ LifecycleStage,
26
+ PlanError,
27
+ build_plan,
28
+ canonical_json,
29
+ validate_lifecycle_stages,
30
+ )
31
+
32
+ __all__ = [
33
+ "ArtifactSource", "BootstrapPlan", "BootstrapRequest", "LifecycleStage",
34
+ "PlanError", "build_plan", "canonical_json", "validate_lifecycle_stages",
35
+ ]
@@ -0,0 +1,234 @@
1
+ """Strict, side-effect-free bootstrap request and plan models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import json
7
+ from pathlib import PurePosixPath, PureWindowsPath
8
+ import re
9
+ from typing import Any
10
+
11
+ REQUEST_SCHEMA = "cf.bootstrap.request.v1"
12
+ PLAN_SCHEMA = "cf.bootstrap.plan.v1"
13
+ REQUEST_SCHEMA_V2 = "cf.bootstrap.request.v2"
14
+ PLAN_SCHEMA_V2 = "cf.bootstrap.plan.v2"
15
+ PROTOCOL = "cf.bootstrap.provider.v1"
16
+ STAGES = (
17
+ "validate-inputs",
18
+ "select-artifact-source",
19
+ "prepare-installation",
20
+ "install-artifacts",
21
+ "verify-installation",
22
+ "handoff-to-service",
23
+ )
24
+ MAX_INSTANCE_ID_LENGTH = 64
25
+ _IDENTIFIER = re.compile(r"[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?\Z")
26
+ _VERSION = re.compile(r"[0-9]+(?:\.[0-9]+){0,2}(?:[-+][A-Za-z0-9.-]+)?\Z")
27
+ _IRI = re.compile(r"[A-Za-z][A-Za-z0-9+.-]*:[^\s]+\Z")
28
+
29
+
30
+ class PlanError(ValueError):
31
+ """A request cannot be represented as a canonical plan."""
32
+
33
+
34
+ def _object(value: object, fields: set[str], label: str) -> dict[str, Any]:
35
+ if not isinstance(value, dict) or any(not isinstance(k, str) for k in value):
36
+ raise PlanError(f"{label} must be an object")
37
+ unknown = set(value) - fields
38
+ if unknown:
39
+ raise PlanError(f"{label} contains unknown fields: {sorted(unknown)}")
40
+ return value
41
+
42
+
43
+ def _text(value: object, label: str, pattern: re.Pattern[str] | None = None) -> str:
44
+ if not isinstance(value, str) or not value or value != value.strip() or "\x00" in value:
45
+ raise PlanError(f"{label} must be a nonempty string")
46
+ if pattern and not pattern.fullmatch(value):
47
+ raise PlanError(f"invalid {label}")
48
+ return value
49
+
50
+
51
+ def _canonical_path(value: object, label: str) -> str:
52
+ text = _text(value, label)
53
+ if "://" in text:
54
+ raise PlanError(f"invalid {label}")
55
+ windows = PureWindowsPath(text)
56
+ is_windows = bool(windows.drive) or "\\" in text
57
+ path = windows if is_windows else PurePosixPath(text)
58
+ if not path.is_absolute() or str(path) == path.anchor:
59
+ raise PlanError(f"{label} must be an absolute non-root path")
60
+ # PurePath removes ``.`` lexically; ``..`` is rejected before containment.
61
+ if ".." in path.parts:
62
+ raise PlanError(f"{label} cannot contain '..' segments")
63
+ normalized = str(path).replace("/", "\\") if is_windows else str(path)
64
+ return normalized
65
+
66
+
67
+ def _path_contains(parent: str, child: str) -> bool:
68
+ wp, wc = PureWindowsPath(parent), PureWindowsPath(child)
69
+ if wp.drive or wc.drive:
70
+ if wp.drive.lower() != wc.drive.lower():
71
+ return False
72
+ try:
73
+ wc.relative_to(wp)
74
+ return True
75
+ except ValueError:
76
+ return False
77
+ try:
78
+ PurePosixPath(child).relative_to(PurePosixPath(parent))
79
+ return True
80
+ except ValueError:
81
+ return False
82
+
83
+
84
+ def _identity(value: object) -> str:
85
+ result = _text(value, "instance_id", _IDENTIFIER)
86
+ if len(result) > MAX_INSTANCE_ID_LENGTH:
87
+ raise PlanError(f"instance_id must be at most {MAX_INSTANCE_ID_LENGTH} ASCII characters")
88
+ return result
89
+
90
+
91
+ @dataclass(frozen=True, slots=True)
92
+ class ArtifactSource:
93
+ mode: str
94
+ data: dict[str, str]
95
+
96
+ @classmethod
97
+ def from_dict(cls, value: object) -> "ArtifactSource":
98
+ raw = _object(value, {"mode", "checkout_path", "package", "version"}, "artifact_source")
99
+ mode = raw.get("mode")
100
+ if mode == "local-repository":
101
+ if set(raw) != {"mode", "checkout_path"}:
102
+ raise PlanError("local-repository source requires only checkout_path")
103
+ return cls(mode, {"checkout_path": _canonical_path(raw["checkout_path"], "checkout_path")})
104
+ if mode == "pypi":
105
+ if set(raw) != {"mode", "package", "version"}:
106
+ raise PlanError("pypi source requires package and version")
107
+ package = _text(raw["package"], "package", _IDENTIFIER)
108
+ version = _text(raw["version"], "version", _VERSION)
109
+ return cls(mode, {"package": package, "version": version})
110
+ raise PlanError("source mode must be local-repository or pypi")
111
+
112
+ def as_dict(self) -> dict[str, str]:
113
+ return {"mode": self.mode, **self.data}
114
+
115
+
116
+ @dataclass(frozen=True, slots=True)
117
+ class LifecycleStage:
118
+ name: str
119
+ ordinal: int
120
+ handoff: bool = False
121
+
122
+ def as_dict(self) -> dict[str, Any]:
123
+ return {"name": self.name, "ordinal": self.ordinal, "handoff": self.handoff}
124
+
125
+
126
+ def validate_lifecycle_stages(value: object) -> tuple[LifecycleStage, ...]:
127
+ if not isinstance(value, list) or any(not isinstance(item, dict) for item in value):
128
+ raise PlanError("stages must be a list of objects")
129
+ names: list[str] = []
130
+ result: list[LifecycleStage] = []
131
+ for index, item in enumerate(value):
132
+ raw = _object(item, {"name", "ordinal", "handoff"}, "stage")
133
+ name = _text(raw.get("name"), "stage name")
134
+ ordinal = raw.get("ordinal")
135
+ handoff = raw.get("handoff")
136
+ if name not in STAGES or name in names:
137
+ raise PlanError("stages contain an unknown or duplicate stage")
138
+ if type(ordinal) is not int or type(handoff) is not bool:
139
+ raise PlanError("stage ordinal and handoff marker have invalid types")
140
+ if ordinal != index or handoff is not (name == "handoff-to-service"):
141
+ raise PlanError("stage ordinal or handoff marker is invalid")
142
+ names.append(name)
143
+ result.append(LifecycleStage(name, index, name == "handoff-to-service"))
144
+ if tuple(names) != STAGES:
145
+ raise PlanError("stages must contain the canonical ordered lifecycle including handoff-to-service")
146
+ return tuple(result)
147
+
148
+
149
+ @dataclass(frozen=True, slots=True)
150
+ class BootstrapRequest:
151
+ instance_id: str
152
+ installation_root: str
153
+ cogniflow_home: str
154
+ artifact_source: ArtifactSource
155
+
156
+ @classmethod
157
+ def from_dict(cls, value: object) -> "BootstrapRequest":
158
+ raw = _object(value, {"schema", "instance_id", "installation_root", "cogniflow_home", "artifact_source"}, "request")
159
+ if raw.get("schema") != REQUEST_SCHEMA:
160
+ raise PlanError("unsupported request schema version")
161
+ home = _canonical_path(raw.get("cogniflow_home"), "cogniflow_home")
162
+ install = _canonical_path(raw.get("installation_root"), "installation_root")
163
+ if install == home or _path_contains(home, install):
164
+ raise PlanError("installation_root must be outside Cogniflow home")
165
+ return cls(_identity(raw.get("instance_id")), install, home, ArtifactSource.from_dict(raw.get("artifact_source")))
166
+
167
+
168
+ @dataclass(frozen=True, slots=True)
169
+ class BootstrapRequestV2(BootstrapRequest):
170
+ profile: str
171
+
172
+ @classmethod
173
+ def from_dict(cls, value: object) -> "BootstrapRequestV2":
174
+ raw = _object(value, {"schema", "instance_id", "installation_root", "cogniflow_home", "artifact_source", "profile"}, "request")
175
+ if raw.get("schema") != REQUEST_SCHEMA_V2:
176
+ raise PlanError("unsupported request schema version")
177
+ profile = _text(raw.get("profile"), "profile")
178
+ if not _IRI.fullmatch(profile):
179
+ raise PlanError("profile must be an absolute IRI")
180
+ base = BootstrapRequest.from_dict({key: value for key, value in raw.items() if key != "profile"} | {"schema": REQUEST_SCHEMA})
181
+ return cls(base.instance_id, base.installation_root, base.cogniflow_home, base.artifact_source, profile)
182
+
183
+
184
+ @dataclass(frozen=True, slots=True)
185
+ class BootstrapPlan:
186
+ instance_id: str
187
+ installation_root: str
188
+ cogniflow_home: str
189
+ artifact_source: ArtifactSource
190
+ stages: tuple[LifecycleStage, ...]
191
+
192
+ def as_dict(self) -> dict[str, Any]:
193
+ return {
194
+ "schema": PLAN_SCHEMA,
195
+ "instance_id": self.instance_id,
196
+ "installation_root": self.installation_root,
197
+ "cogniflow_home": self.cogniflow_home,
198
+ "artifact_source": self.artifact_source.as_dict(),
199
+ "stages": [stage.as_dict() for stage in self.stages],
200
+ }
201
+
202
+
203
+ @dataclass(frozen=True, slots=True)
204
+ class BootstrapPlanV2(BootstrapPlan):
205
+ profile: str
206
+
207
+ def as_dict(self) -> dict[str, Any]:
208
+ return {
209
+ "schema": PLAN_SCHEMA_V2,
210
+ "instance_id": self.instance_id,
211
+ "installation_root": self.installation_root,
212
+ "cogniflow_home": self.cogniflow_home,
213
+ "artifact_source": self.artifact_source.as_dict(),
214
+ "profile": self.profile,
215
+ "stages": [stage.as_dict() for stage in self.stages],
216
+ }
217
+
218
+
219
+ def build_plan(request: BootstrapRequest | dict[str, Any]) -> BootstrapPlan:
220
+ if isinstance(request, (BootstrapRequest, BootstrapRequestV2)):
221
+ checked = request
222
+ elif isinstance(request, dict) and request.get("schema") == REQUEST_SCHEMA_V2:
223
+ checked = BootstrapRequestV2.from_dict(request)
224
+ else:
225
+ checked = BootstrapRequest.from_dict(request)
226
+ stages = tuple(LifecycleStage(name, index, name == "handoff-to-service") for index, name in enumerate(STAGES))
227
+ if isinstance(checked, BootstrapRequestV2):
228
+ return BootstrapPlanV2(checked.instance_id, checked.installation_root, checked.cogniflow_home, checked.artifact_source, stages, checked.profile)
229
+ return BootstrapPlan(checked.instance_id, checked.installation_root, checked.cogniflow_home, checked.artifact_source, stages)
230
+
231
+
232
+ def canonical_json(value: object) -> bytes:
233
+ payload = value.as_dict() if hasattr(value, "as_dict") else value
234
+ return (json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
@@ -0,0 +1,65 @@
1
+ """Internal versioned JSON-lines bootstrap-plan provider."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+
8
+ from .model import PROTOCOL, REQUEST_SCHEMA, PlanError, build_plan
9
+
10
+ _FIELDS = {"jsonrpc", "protocol", "id", "method", "params"}
11
+ _REQUEST_FIELDS = {"schema", "instance_id", "installation_root", "cogniflow_home", "artifact_source", "profile"}
12
+
13
+
14
+ def _envelope(request_id: object, **body: object) -> dict[str, object]:
15
+ return {"jsonrpc": "2.0", "protocol": PROTOCOL, "id": request_id, **body}
16
+
17
+
18
+ def handle(payload: object) -> dict[str, object]:
19
+ request_id: object = None
20
+ try:
21
+ if not isinstance(payload, dict):
22
+ return _envelope(None, error={"code": "INVALID_ENVELOPE", "message": "request envelope must be an object"})
23
+ unknown = set(payload) - _FIELDS
24
+ if unknown:
25
+ return _envelope(payload.get("id"), error={"code": "UNKNOWN_ENVELOPE_FIELD", "message": "request envelope contains unknown fields"})
26
+ missing = _FIELDS - set(payload)
27
+ if missing:
28
+ return _envelope(payload.get("id"), error={"code": "INVALID_ENVELOPE", "message": "request envelope is missing required fields"})
29
+ request_id = payload["id"]
30
+ if payload["jsonrpc"] != "2.0" or not isinstance(request_id, (str, int)) or isinstance(request_id, bool):
31
+ return _envelope(request_id, error={"code": "INVALID_ENVELOPE", "message": "request envelope field types are invalid"})
32
+ if not isinstance(payload["protocol"], str) or not isinstance(payload["method"], str):
33
+ return _envelope(request_id, error={"code": "INVALID_ENVELOPE", "message": "request envelope field types are invalid"})
34
+ if payload["protocol"] != PROTOCOL:
35
+ return _envelope(request_id, error={"code": "PROTOCOL_VERSION_UNSUPPORTED", "message": "unsupported bootstrap protocol version"})
36
+ if payload["method"] != "bootstrap/plan":
37
+ return _envelope(request_id, error={"code": "UNKNOWN_OPERATION", "message": "unknown bootstrap operation"})
38
+ params = payload["params"]
39
+ if isinstance(params, dict) and params.get("schema") == REQUEST_SCHEMA and "profile" in params:
40
+ return _envelope(request_id, error={"code": "SCHEMA_INVALID", "message": "v1 request cannot contain profile"})
41
+ if isinstance(params, dict) and (set(params) - _REQUEST_FIELDS):
42
+ return _envelope(request_id, error={"code": "UNKNOWN_REQUEST_FIELD", "message": "request body contains unknown fields"})
43
+ return _envelope(request_id, result=build_plan(params).as_dict())
44
+ except PlanError as error:
45
+ return _envelope(request_id, error={"code": "SCHEMA_INVALID", "message": str(error)})
46
+ except Exception:
47
+ return _envelope(request_id, error={"code": "INTERNAL_ERROR", "message": "bootstrap provider failed unexpectedly"})
48
+
49
+
50
+ def main() -> int:
51
+ # Exit code 0 is stable for every processed line, including structured errors.
52
+ for line in sys.stdin:
53
+ try:
54
+ payload = json.loads(line)
55
+ except json.JSONDecodeError:
56
+ response = _envelope(None, error={"code": "MALFORMED_JSON", "message": "request must be valid JSON"})
57
+ else:
58
+ response = handle(payload)
59
+ sys.stdout.write(json.dumps(response, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n")
60
+ sys.stdout.flush()
61
+ return 0
62
+
63
+
64
+ if __name__ == "__main__":
65
+ 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_core {
8
+ pkg:cf_bootstrap_core a cfpkg:CfPackage ;
9
+ cfpkg:hasPackageManifest [ a cfpkg:PackageManifest ; cfpkg:hasDistributionName [ a cfpkg:DistributionName ; rdf:value "cf-bootstrap-core" ] ; cfpkg:hasPythonPackageName [ a cfpkg:PythonPackageName ; rdf:value "cf_bootstrap_core" ] ; cfpkg:hasPackageVersion [ a cfpkg:PackageVersion ; rdf:value "0.1.8" ] ; 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-core" ; 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,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: cf-bootstrap-core
3
+ Version: 0.1.8
4
+ Summary: Pure canonical Cogniflow bootstrap lifecycle and instance-plan model.
5
+ Author: Cogniflow Maintainers
6
+ License: GPL-3
7
+ Requires-Python: >=3.11
8
+ Provides-Extra: test
9
+ Requires-Dist: pytest>=8; extra == 'test'
10
+ Description-Content-Type: text/markdown
11
+
12
+ # cf-bootstrap-core
13
+
14
+ `cf_bootstrap_core` is a pure, standard-library-only model for the deterministic bootstrap plan. It validates versioned requests and returns a canonical plan without creating files, directories, locks, environments, manifests, or runtime state.
15
+
16
+ ## Boundary
17
+
18
+ A bootstrap plan is intended lifecycle data before side effects: identity, caller-supplied Cogniflow home context, external installation root, artifact source, and the ordered stages `validate-inputs`, `select-artifact-source`, `prepare-installation`, `install-artifacts`, `verify-installation`, and `handoff-to-service`. The handoff is ordinally explicit and is the end of imperative bootstrap responsibility. Stage names describe future work only; this package does not perform it.
19
+
20
+ A runtime manifest is operational state owned solely by `cf_runtime`. This package never creates or interprets one, writes beneath `.cogniflow`, duplicates `CogniflowHome` or `RuntimePaths`, or derives the internal `.cogniflow` layout. The installation root is an explicit external location and is not created during planning. Paths are validated lexically without filesystem access; `.` segments are normalized by `PurePath`, while `..` segments are rejected before component-aware containment, so nonexistent targets remain valid and traversal cannot bypass containment.
21
+
22
+ ## Identity and protocol
23
+
24
+ `instance_id` is caller-supplied ASCII text of 1-64 characters matching `[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?`. It is case-sensitive, may contain internal dots, hyphens, and underscores, and has no path or environment meaning. No random, timestamp, process, host, working-directory, or checkout-derived identity is generated.
25
+
26
+ The package-local `cf-bootstrap-core-provider` accepts `cf.bootstrap.provider.v1` JSON-RPC-shaped JSON-lines requests with method `bootstrap/plan`. Each input line produces one response line. Exit code is `0` for empty input, successful requests, schema/protocol errors, malformed JSON, and unexpected internal errors after the lines are processed. stdout contains only compact canonical JSON; diagnostics are not written to stdout. Error codes are `MALFORMED_JSON`, `INVALID_ENVELOPE`, `PROTOCOL_VERSION_UNSUPPORTED`, `UNKNOWN_OPERATION`, `SCHEMA_INVALID`, and `INTERNAL_ERROR`.
27
+
28
+ `UNKNOWN_ENVELOPE_FIELD` means an envelope contains an unrecognized field; `INVALID_ENVELOPE` means its object shape, required fields, or field types are invalid; `UNKNOWN_REQUEST_FIELD` means the plan request body contains an unrecognized field; `SCHEMA_INVALID` means the request values violate the versioned model. `MALFORMED_JSON`, `PROTOCOL_VERSION_UNSUPPORTED`, `UNKNOWN_OPERATION`, and `INTERNAL_ERROR` retain their literal meanings.
29
+
30
+ Canonical JSON is UTF-8 JSON with sorted object keys, compact separators, deterministic list order, no volatile values, and exactly one LF terminator. Only `local-repository` and `pypi` source modes are accepted. No package is built, resolved, downloaded, installed, or started.
31
+
32
+ The public `cogniflow` launcher discovers this provider through the
33
+ `cogniflow.bootstrap_providers.v1` distribution entry-point group and invokes
34
+ the matching `cf-bootstrap-core-provider` console script as an external
35
+ process. The launcher does not import this package. Artifact resolver and
36
+ frontend discovery groups are reserved as `cogniflow.artifact_resolvers.v1`
37
+ and `cogniflow.frontends.v1`; they are not implemented here.
@@ -0,0 +1,8 @@
1
+ cf_bootstrap_core/__init__.py,sha256=klFSZWs6XvEgZyrlvYvO9yudMT3ftPYhUl9thpJBOeE,802
2
+ cf_bootstrap_core/model.py,sha256=N0Y_g-yUzNKFDJOBIfHygE0ekKw4yEtstDMZOQs9CNE,9810
3
+ cf_bootstrap_core/provider.py,sha256=6e0vYGISRbd8nwv9L2Rescdgbaypo3Z1aMm_z3J5iyE,3517
4
+ cf_bootstrap_core/semantics/package.trig,sha256=8nbEh50HlUAC2fgG53YaGe0wkNf95H1plBqeIbhHD7E,1208
5
+ cf_bootstrap_core-0.1.8.dist-info/METADATA,sha256=aErPfztwF2SRf9GFoLQGiPCBr36Vc7BKaemjQCBcBXA,3743
6
+ cf_bootstrap_core-0.1.8.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
7
+ cf_bootstrap_core-0.1.8.dist-info/entry_points.txt,sha256=Y8GzfgXclRNi9yeIu8hWhdg6nTQgwJ6ugSKB5Rwqy0c,259
8
+ cf_bootstrap_core-0.1.8.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,8 @@
1
+ [console_scripts]
2
+ cf-bootstrap-core-provider = cf_bootstrap_core.provider:main
3
+
4
+ [cogniflow.bootstrap_providers.v1]
5
+ cf-bootstrap-core-provider = cf_bootstrap_core.provider:main
6
+
7
+ [cogniflow.semantic_sources]
8
+ cf-bootstrap-core = cf_bootstrap_core:semantic_files