tecrax 0.2.2a0__tar.gz → 0.3.2a0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {tecrax-0.2.2a0 → tecrax-0.3.2a0}/.github/workflows/ci.yml +5 -0
  2. {tecrax-0.2.2a0 → tecrax-0.3.2a0}/PKG-INFO +32 -13
  3. tecrax-0.3.2a0/PUBLIC_STATUS.md +7 -0
  4. {tecrax-0.2.2a0 → tecrax-0.3.2a0}/README.md +25 -9
  5. {tecrax-0.2.2a0 → tecrax-0.3.2a0}/VALIDATION.md +5 -3
  6. {tecrax-0.2.2a0 → tecrax-0.3.2a0}/pyproject.toml +18 -4
  7. {tecrax-0.2.2a0 → tecrax-0.3.2a0}/scripts/validate_public_truth.py +23 -5
  8. tecrax-0.3.2a0/src/tecrax/__init__.py +16 -0
  9. {tecrax-0.2.2a0 → tecrax-0.3.2a0}/src/tecrax/cli.py +1 -1
  10. tecrax-0.3.2a0/src/tecrax/connectors/__init__.py +1 -0
  11. tecrax-0.3.2a0/src/tecrax/connectors/proxmox.py +90 -0
  12. tecrax-0.3.2a0/src/tecrax/connectors/proxmox_runtime.py +38 -0
  13. tecrax-0.3.2a0/src/tecrax/fixture/__init__.py +0 -0
  14. tecrax-0.3.2a0/src/tecrax/fixture/mock_runtime.py +67 -0
  15. tecrax-0.3.2a0/src/tecrax/internal_actions.py +75 -0
  16. tecrax-0.3.2a0/src/tecrax/profile/connectors/pbs.yaml +7 -0
  17. tecrax-0.3.2a0/src/tecrax/profile/connectors/proxmox.yaml +9 -0
  18. tecrax-0.3.2a0/src/tecrax/profile/intents/check_backup_status.yaml +7 -0
  19. tecrax-0.3.2a0/src/tecrax/profile/intents/restart_zabbix_agent.yaml +6 -0
  20. tecrax-0.3.2a0/src/tecrax/profile/profile.yaml +30 -0
  21. tecrax-0.3.2a0/src/tecrax/profile/validation_rules/check_backup_status.yaml +13 -0
  22. tecrax-0.3.2a0/src/tecrax/profile/validation_rules/restart_zabbix_agent.yaml +18 -0
  23. tecrax-0.3.2a0/src/tecrax/profile/workflows/check_backup_status.yaml +42 -0
  24. tecrax-0.3.2a0/src/tecrax/profile/workflows/restart_zabbix_agent.yaml +45 -0
  25. {tecrax-0.2.2a0 → tecrax-0.3.2a0}/tests/test_local_fixture.py +6 -3
  26. tecrax-0.3.2a0/tests/test_proxmox_connector_backend.py +32 -0
  27. tecrax-0.3.2a0/tests/test_proxmox_connector_templates.py +16 -0
  28. tecrax-0.3.2a0/tests/test_rexecop_profile.py +35 -0
  29. tecrax-0.2.2a0/PUBLIC_STATUS.md +0 -23
  30. tecrax-0.2.2a0/src/tecrax/__init__.py +0 -7
  31. {tecrax-0.2.2a0 → tecrax-0.3.2a0}/.gitignore +0 -0
  32. {tecrax-0.2.2a0 → tecrax-0.3.2a0}/LICENSE +0 -0
  33. {tecrax-0.2.2a0 → tecrax-0.3.2a0}/src/tecrax/local_fixture.py +0 -0
@@ -20,6 +20,10 @@ jobs:
20
20
  python-version: ['3.11', '3.12']
21
21
  steps:
22
22
  - uses: actions/checkout@v6
23
+ - uses: actions/checkout@v6
24
+ with:
25
+ repository: rozmiarD/RExecOP
26
+ path: rexecop
23
27
  - uses: actions/setup-python@v6
24
28
  with:
25
29
  python-version: ${{ matrix.python-version }}
@@ -28,6 +32,7 @@ jobs:
28
32
  python -m pip install --upgrade pip
29
33
  python -m pip install "sclite-core @ git+https://github.com/rozmiarD/SCLite.git@main"
30
34
  python -m pip install "govengine @ git+https://github.com/rozmiarD/GovEngine.git@main"
35
+ python -m pip install -e ./rexecop
31
36
  python -m pip install -e '.[dev]'
32
37
  - name: Validate public truth
33
38
  run: python scripts/validate_public_truth.py
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tecrax
3
- Version: 0.2.2a0
4
- Summary: Dry-run local-fixture infrastructure-operations profile over GovEngine and SCLite.
3
+ Version: 0.3.2a0
4
+ Summary: Governed infrastructure-operations profile and RExecOp domain package over GovEngine and SCLite.
5
5
  Project-URL: Homepage, https://github.com/rozmiarD/tecrax
6
6
  Project-URL: Repository, https://github.com/rozmiarD/tecrax
7
7
  Author: Krzysztof Probola
@@ -17,22 +17,30 @@ Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Topic :: System :: Systems Administration
19
19
  Requires-Python: >=3.11
20
- Requires-Dist: govengine<0.12,>=0.11.0a0
21
- Requires-Dist: sclite-core<0.9,>=0.8.0a0
20
+ Requires-Dist: govengine<0.15,>=0.12.2a0
21
+ Requires-Dist: rexecop<0.3,>=0.2.2a0
22
+ Requires-Dist: sclite-core<1.1,>=1.0.1
22
23
  Provides-Extra: dev
23
24
  Requires-Dist: pytest<9,>=8; extra == 'dev'
25
+ Provides-Extra: fixture
26
+ Requires-Dist: rexecop<0.2,>=0.1.2a0; extra == 'fixture'
24
27
  Description-Content-Type: text/markdown
25
28
 
26
29
  # Tecrax
27
30
 
28
31
  Tecrax is a governed infrastructure-operations runtime/profile built on GovEngine and SCLite.
29
32
 
30
- Current source/package baseline: `tecrax==0.2.2a0`, depending on
31
- `govengine>=0.11.0a0,<0.12` and `sclite-core>=0.8.0a0,<0.9`.
33
+ Current published baseline: `tecrax==0.3.2a0`, depending on
34
+ `govengine>=0.12.2a0,<0.15`, `sclite-core>=1.0.1,<1.1`, and `rexecop>=0.2.2a0,<0.3`.
32
35
 
33
- This repository/package contains a dry-run/local-fixture profile slice. It still
34
- does not execute infrastructure changes, connect to hosts, manage credentials,
35
- or provide production operational capability.
36
+ This package provides:
37
+
38
+ - **RExecOp domain profile** — bundled YAML profile with intents, workflows, connectors,
39
+ and validation rules (entry point `rexecop.profiles:tecrax`).
40
+ - **Local fixture review** — dry-run proof slice without live infrastructure.
41
+
42
+ It does not execute infrastructure changes, connect to hosts, manage credentials,
43
+ or provide production operational capability without explicit operator configuration.
36
44
 
37
45
  Planned foundation:
38
46
 
@@ -45,7 +53,18 @@ Tecrax -> GovEngine -> SCLite
45
53
  - Tecrax owns the infrastructure-operations profile semantics, fixture review
46
54
  payloads, UX, and future host integrations when those boundaries are mature.
47
55
 
48
- Current local fixture proof:
56
+ ## RExecOp profile
57
+
58
+ Install `tecrax` alongside `rexecop` to register the domain profile:
59
+
60
+ ```bash
61
+ pip install rexecop tecrax
62
+ rexecop profile list
63
+ ```
64
+
65
+ The profile root is exposed via `tecrax:profile_root` (directory `src/tecrax/profile/`).
66
+
67
+ ## Local fixture proof
49
68
 
50
69
  ```bash
51
70
  tecrax fixture-review --service demo-web
@@ -56,9 +75,9 @@ profile/planning/supervision/runtime-review contracts and binds its fixture
56
75
  receipt through an SCLite artifact descriptor. It has no live runner, host
57
76
  inventory, credential path, or infrastructure adapter.
58
77
 
59
- The `0.2.2-alpha` patch only aligns this fixture consumer with the curated
60
- SCLite/GovEngine package chain. It does not add an infrastructure runtime or a
61
- new contract surface.
78
+ The `0.3.1-alpha` release consolidates the RExecOp domain profile into this
79
+ package and aligns dependencies with RExecOp `0.1.x`. It does not add an
80
+ infrastructure runtime or a new contract surface beyond the bundled profile.
62
81
 
63
82
  ## Validation
64
83
 
@@ -0,0 +1,7 @@
1
+ # Tecrax Public Status
2
+
3
+ - **Package version:** `0.3.2a0` (`0.3.1-alpha`)
4
+ - **Dependencies:** `govengine>=0.12.2a0,<0.15`, `sclite-core>=1.0.1,<1.1`, `rexecop>=0.2.2a0,<0.3`
5
+ - **RExecOp profile:** bundled at `src/tecrax/profile/` via `rexecop.profiles:tecrax`
6
+ - **Local fixture:** `tecrax fixture-review` — dry-run GovEngine/SCLite proof only
7
+ - **Not claimed:** live infrastructure runner, host inventory, credentials, carrier adapters, scheduler/storage, production readiness
@@ -2,12 +2,17 @@
2
2
 
3
3
  Tecrax is a governed infrastructure-operations runtime/profile built on GovEngine and SCLite.
4
4
 
5
- Current source/package baseline: `tecrax==0.2.2a0`, depending on
6
- `govengine>=0.11.0a0,<0.12` and `sclite-core>=0.8.0a0,<0.9`.
5
+ Current published baseline: `tecrax==0.3.2a0`, depending on
6
+ `govengine>=0.12.2a0,<0.15`, `sclite-core>=1.0.1,<1.1`, and `rexecop>=0.2.2a0,<0.3`.
7
7
 
8
- This repository/package contains a dry-run/local-fixture profile slice. It still
9
- does not execute infrastructure changes, connect to hosts, manage credentials,
10
- or provide production operational capability.
8
+ This package provides:
9
+
10
+ - **RExecOp domain profile** — bundled YAML profile with intents, workflows, connectors,
11
+ and validation rules (entry point `rexecop.profiles:tecrax`).
12
+ - **Local fixture review** — dry-run proof slice without live infrastructure.
13
+
14
+ It does not execute infrastructure changes, connect to hosts, manage credentials,
15
+ or provide production operational capability without explicit operator configuration.
11
16
 
12
17
  Planned foundation:
13
18
 
@@ -20,7 +25,18 @@ Tecrax -> GovEngine -> SCLite
20
25
  - Tecrax owns the infrastructure-operations profile semantics, fixture review
21
26
  payloads, UX, and future host integrations when those boundaries are mature.
22
27
 
23
- Current local fixture proof:
28
+ ## RExecOp profile
29
+
30
+ Install `tecrax` alongside `rexecop` to register the domain profile:
31
+
32
+ ```bash
33
+ pip install rexecop tecrax
34
+ rexecop profile list
35
+ ```
36
+
37
+ The profile root is exposed via `tecrax:profile_root` (directory `src/tecrax/profile/`).
38
+
39
+ ## Local fixture proof
24
40
 
25
41
  ```bash
26
42
  tecrax fixture-review --service demo-web
@@ -31,9 +47,9 @@ profile/planning/supervision/runtime-review contracts and binds its fixture
31
47
  receipt through an SCLite artifact descriptor. It has no live runner, host
32
48
  inventory, credential path, or infrastructure adapter.
33
49
 
34
- The `0.2.2-alpha` patch only aligns this fixture consumer with the curated
35
- SCLite/GovEngine package chain. It does not add an infrastructure runtime or a
36
- new contract surface.
50
+ The `0.3.1-alpha` release consolidates the RExecOp domain profile into this
51
+ package and aligns dependencies with RExecOp `0.1.x`. It does not add an
52
+ infrastructure runtime or a new contract surface beyond the bundled profile.
37
53
 
38
54
  ## Validation
39
55
 
@@ -9,10 +9,12 @@ python -m pytest -q
9
9
  tecrax fixture-review --service demo-web
10
10
  ```
11
11
 
12
- Expected result for `0.2.2a0`:
12
+ Expected result for `0.3.2a0`:
13
13
 
14
- - `pyproject.toml`, `tecrax.__version__`, README, public status, and validators agree on `0.2.2a0` / `0.2.2-alpha`;
15
- - dependency truth is `govengine>=0.11.0a0,<0.12` and `sclite-core>=0.8.0a0,<0.9`;
14
+ - `pyproject.toml`, `tecrax.__version__`, README, public status, and validators agree on `0.3.2a0` / `0.3.1-alpha`;
15
+ - PyPI publication remains a fixture-only alpha package claim, not an infrastructure runtime claim;
16
+ - dependency truth is `govengine>=0.12.2a0,<0.15`, `sclite-core>=1.0.1,<1.1`, and `rexecop>=0.2.2a0,<0.3`;
17
+ - RExecOp profile entry point `tecrax:profile_root` resolves to a valid profile bundle;
16
18
  - fixture review output validates GovEngine profile, planning, supervision, runtime snapshot, review result, and runtime contract proof objects;
17
19
  - SCLite is used only for local artifact descriptors;
18
20
  - non-claims remain explicit for live infrastructure, credentials, adapters, scheduler/storage, and production readiness.
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tecrax"
7
- version = "0.2.2a0"
8
- description = "Dry-run local-fixture infrastructure-operations profile over GovEngine and SCLite."
7
+ version = "0.3.2a0"
8
+ description = "Governed infrastructure-operations profile and RExecOp domain package over GovEngine and SCLite."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
11
11
  license = { text = "MIT" }
@@ -22,10 +22,21 @@ classifiers = [
22
22
  "Topic :: System :: Systems Administration",
23
23
  ]
24
24
  dependencies = [
25
- "govengine>=0.11.0a0,<0.12",
26
- "sclite-core>=0.8.0a0,<0.9",
25
+ "govengine>=0.12.2a0,<0.15",
26
+ "sclite-core>=1.0.1,<1.1",
27
+ "rexecop>=0.2.2a0,<0.3",
27
28
  ]
28
29
 
30
+ [project.entry-points."rexecop.profiles"]
31
+ tecrax = "tecrax:profile_root"
32
+
33
+ [project.entry-points."rexecop.internal_actions"]
34
+ tecrax = "tecrax.internal_actions:register_handlers"
35
+
36
+ [project.entry-points."rexecop.connector_backends"]
37
+ tecrax_fixture = "tecrax.fixture.mock_runtime:build_runtime"
38
+ tecrax_proxmox = "tecrax.connectors.proxmox_runtime:build_connector_runtime"
39
+
29
40
  [project.urls]
30
41
  Homepage = "https://github.com/rozmiarD/tecrax"
31
42
  Repository = "https://github.com/rozmiarD/tecrax"
@@ -37,6 +48,9 @@ tecrax = "tecrax.cli:main"
37
48
  dev = [
38
49
  "pytest>=8,<9",
39
50
  ]
51
+ fixture = [
52
+ "rexecop>=0.1.2a0,<0.2",
53
+ ]
40
54
 
41
55
  [tool.hatch.build.targets.wheel]
42
56
  packages = ["src/tecrax"]
@@ -14,10 +14,11 @@ import tecrax # noqa: E402
14
14
  from tecrax.local_fixture import build_local_fixture_review # noqa: E402
15
15
 
16
16
 
17
- EXPECTED_VERSION = '0.2.2a0'
18
- EXPECTED_RELEASE_LABEL = '0.2.2-alpha'
19
- EXPECTED_GOVENGINE = 'govengine>=0.11.0a0,<0.12'
20
- EXPECTED_SCLITE = 'sclite-core>=0.8.0a0,<0.9'
17
+ EXPECTED_VERSION = '0.3.2a0'
18
+ EXPECTED_RELEASE_LABEL = '0.3.1-alpha'
19
+ EXPECTED_GOVENGINE = 'govengine>=0.12.2a0,<0.15'
20
+ EXPECTED_SCLITE = 'sclite-core>=1.0.1,<1.1'
21
+ EXPECTED_REXECOP = 'rexecop>=0.2.2a0,<0.3'
21
22
  PUBLIC_DOCS = (
22
23
  'README.md',
23
24
  'PUBLIC_STATUS.md',
@@ -65,6 +66,7 @@ def collect_errors() -> list[str]:
65
66
  version = str(project['version'])
66
67
  govengine_dep = _dependency(project, 'govengine')
67
68
  sclite_dep = _dependency(project, 'sclite-core')
69
+ rexecop_dep = _dependency(project, 'rexecop')
68
70
 
69
71
  if project['name'] != 'tecrax':
70
72
  errors.append(f'distribution_name_mismatch:{project["name"]}')
@@ -76,13 +78,22 @@ def collect_errors() -> list[str]:
76
78
  errors.append(f'govengine_dependency_mismatch:{govengine_dep}!={EXPECTED_GOVENGINE}')
77
79
  if sclite_dep != EXPECTED_SCLITE:
78
80
  errors.append(f'sclite_dependency_mismatch:{sclite_dep}!={EXPECTED_SCLITE}')
81
+ if rexecop_dep != EXPECTED_REXECOP:
82
+ errors.append(f'rexecop_dependency_mismatch:{rexecop_dep}!={EXPECTED_REXECOP}')
79
83
 
80
84
  for path in PUBLIC_DOCS:
81
85
  _require(errors, path, EXPECTED_VERSION)
82
86
  _require(errors, path, EXPECTED_GOVENGINE)
83
87
  _require(errors, path, EXPECTED_SCLITE)
88
+ _require(errors, path, EXPECTED_REXECOP)
84
89
  _require(errors, 'PUBLIC_STATUS.md', EXPECTED_RELEASE_LABEL)
85
90
  _require(errors, 'README.md', 'tecrax fixture-review --service demo-web')
91
+ _require(errors, 'README.md', 'rexecop.profiles:tecrax')
92
+ _require(errors, 'pyproject.toml', 'rexecop.profiles')
93
+ _require(errors, 'pyproject.toml', 'tecrax:profile_root')
94
+ profile_root = Path(tecrax.profile_root())
95
+ if not (profile_root / 'profile.yaml').is_file():
96
+ errors.append('profile_bundle_missing:profile.yaml')
86
97
  _require(errors, 'VALIDATION.md', 'python scripts/validate_public_truth.py')
87
98
  _require(errors, '.github/workflows/ci.yml', 'actions/checkout@v6')
88
99
  _require(errors, '.github/workflows/ci.yml', 'actions/setup-python@v6')
@@ -94,6 +105,8 @@ def collect_errors() -> list[str]:
94
105
  _require(errors, '.github/workflows/ci.yml', 'python -m pip check')
95
106
  _require(errors, '.github/workflows/ci.yml', 'sclite-core @ git+https://github.com/rozmiarD/SCLite.git@main')
96
107
  _require(errors, '.github/workflows/ci.yml', 'govengine @ git+https://github.com/rozmiarD/GovEngine.git@main')
108
+ _require(errors, '.github/workflows/ci.yml', 'repository: rozmiarD/RExecOP')
109
+ _require(errors, '.github/workflows/ci.yml', 'pip install -e ./rexecop')
97
110
 
98
111
  review = build_local_fixture_review('truth-fixture')
99
112
  if review.get('artifact_type') != 'tecrax_local_fixture_review':
@@ -111,6 +124,11 @@ def collect_errors() -> list[str]:
111
124
  errors.append('fixture_claims_credentials')
112
125
  if not review.get('sclite_fixture_receipt_descriptor', {}).get('digest'):
113
126
  errors.append('fixture_missing_sclite_descriptor_digest')
127
+ cli_text = _read('src/tecrax/cli.py')
128
+ if EXPECTED_RELEASE_LABEL not in cli_text:
129
+ errors.append(f'src/tecrax/cli.py:missing_current_release_label:{EXPECTED_RELEASE_LABEL}')
130
+ if '0.2.0-alpha' in cli_text or '0.2.1-alpha' in cli_text:
131
+ errors.append('src/tecrax/cli.py:stale_cli_status_release_label')
114
132
 
115
133
  for path in PUBLIC_DOCS:
116
134
  lowered = _read(path).lower()
@@ -128,7 +146,7 @@ def main() -> int:
128
146
  for error in errors:
129
147
  print(error, file=sys.stderr)
130
148
  return 1
131
- print(f'public_truth_ok:tecrax=={EXPECTED_VERSION}:{EXPECTED_GOVENGINE}:{EXPECTED_SCLITE}')
149
+ print(f'public_truth_ok:tecrax=={EXPECTED_VERSION}:{EXPECTED_GOVENGINE}:{EXPECTED_SCLITE}:{EXPECTED_REXECOP}')
132
150
  return 0
133
151
 
134
152
 
@@ -0,0 +1,16 @@
1
+ """Tecrax governed infrastructure-operations profile and local-fixture runtime."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from .local_fixture import build_local_fixture_review
8
+
9
+ __version__ = "0.3.2a0"
10
+
11
+ __all__ = ["__version__", "build_local_fixture_review", "profile_root"]
12
+
13
+
14
+ def profile_root() -> str:
15
+ """Entry point for rexecop.profiles — returns bundled RExecOp profile directory."""
16
+ return str(Path(__file__).resolve().parent / "profile")
@@ -28,7 +28,7 @@ def main(argv: list[str] | None = None) -> int:
28
28
  return 0
29
29
 
30
30
  print(
31
- "Tecrax 0.2.0-alpha local-fixture profile: no live infrastructure "
31
+ "Tecrax 0.3.1-alpha local-fixture profile: no live infrastructure "
32
32
  "connections, credentials, or operational changes are enabled."
33
33
  )
34
34
  return 0
@@ -0,0 +1 @@
1
+ """Domain connector helpers for Tecrax profiles."""
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ def build_http_api_connector_config(
7
+ *,
8
+ staging_paths: bool = False,
9
+ base_url_secret_ref: str = "proxmox_base_url",
10
+ api_token_secret_ref: str = "proxmox_api_token",
11
+ timeout_seconds: int = 10,
12
+ max_retry_attempts: int = 2,
13
+ ) -> dict[str, Any]:
14
+ """Build environment YAML connector config for Proxmox over generic http_api."""
15
+ if staging_paths:
16
+ list_action: dict[str, Any] = {
17
+ "method": "GET",
18
+ "path": "/proxmox/vms/paged",
19
+ "pagination": {
20
+ "items_path": "data.vms",
21
+ "next_path": "data.next",
22
+ "max_pages": 5,
23
+ },
24
+ }
25
+ restart_path = "/proxmox/restart"
26
+ else:
27
+ list_action = {
28
+ "method": "GET",
29
+ "path": "/api2/json/cluster/resources",
30
+ "unwrap": "data",
31
+ }
32
+ restart_path = "/api2/json/nodes/{node}/qemu/{vmid}/status/restart"
33
+
34
+ actions: dict[str, Any] = {
35
+ "list_vms": list_action,
36
+ "restart": {
37
+ "method": "POST",
38
+ "path": restart_path,
39
+ "mutating": True,
40
+ "body": {"target": "{target}"},
41
+ },
42
+ }
43
+
44
+ return {
45
+ "enabled": True,
46
+ "backend": "http_api",
47
+ "base_url_secret_ref": base_url_secret_ref,
48
+ "auth": {
49
+ "secret_ref": api_token_secret_ref,
50
+ "header": "Authorization",
51
+ "prefix": "PVEAPIToken=",
52
+ },
53
+ "timeout_seconds": timeout_seconds,
54
+ "retry": {
55
+ "max_attempts": max_retry_attempts,
56
+ "base_delay": 0.2,
57
+ "max_delay": 2.0,
58
+ "on": ["timeout", "transient_connector_error"],
59
+ },
60
+ "actions": actions,
61
+ }
62
+
63
+
64
+ def merge_http_api_connector_config(
65
+ template: dict[str, Any],
66
+ overrides: dict[str, Any],
67
+ ) -> dict[str, Any]:
68
+ """Merge operator overrides into a Proxmox http_api template."""
69
+ merged: dict[str, Any] = dict(template)
70
+ skip_keys = {"backend", "plugin", "staging_paths"}
71
+ for key, value in overrides.items():
72
+ if key in skip_keys:
73
+ continue
74
+ if isinstance(value, dict) and isinstance(merged.get(key), dict):
75
+ nested = dict(merged[key])
76
+ nested.update(value)
77
+ merged[key] = nested
78
+ else:
79
+ merged[key] = value
80
+ merged["backend"] = "http_api"
81
+ if "base_url" in overrides:
82
+ merged.pop("base_url_secret_ref", None)
83
+ if "auth" not in overrides:
84
+ merged.pop("auth", None)
85
+ auth = overrides.get("auth")
86
+ if isinstance(auth, dict) and auth.get("secret_ref"):
87
+ merged_auth = dict(merged.get("auth") or {})
88
+ merged_auth.update(auth)
89
+ merged["auth"] = merged_auth
90
+ return merged
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from rexecop.connectors.base import ConnectorRuntime
6
+ from rexecop.connectors.http_api import HttpApiConnectorRuntime
7
+ from rexecop.secrets.port import SecretResolver
8
+ from rexecop.secrets.resolver import default_secret_resolver
9
+
10
+ from tecrax.connectors.proxmox import build_http_api_connector_config, merge_http_api_connector_config
11
+
12
+
13
+ def build_connector_runtime(
14
+ *,
15
+ connector_name: str,
16
+ config: dict[str, Any],
17
+ profile_root: str | None,
18
+ mutating_allowed: bool,
19
+ secret_resolver: SecretResolver | None = None,
20
+ ) -> ConnectorRuntime:
21
+ """Domain connector backend: Proxmox over generic http_api with Tecrax templates."""
22
+ template = build_http_api_connector_config(
23
+ staging_paths=bool(config.get("staging_paths")),
24
+ base_url_secret_ref=str(config.get("base_url_secret_ref") or "proxmox_base_url"),
25
+ api_token_secret_ref=str(config.get("api_token_secret_ref") or "proxmox_api_token"),
26
+ timeout_seconds=int(config.get("timeout_seconds") or 10),
27
+ max_retry_attempts=int((config.get("retry") or {}).get("max_attempts") or 2)
28
+ if isinstance(config.get("retry"), dict)
29
+ else int(config.get("max_retry_attempts") or 2),
30
+ )
31
+ merged = merge_http_api_connector_config(template, config)
32
+ return HttpApiConnectorRuntime(
33
+ connector_name=connector_name,
34
+ config=merged,
35
+ profile_root=profile_root,
36
+ mutating_allowed=mutating_allowed,
37
+ secret_resolver=secret_resolver or default_secret_resolver(),
38
+ )
File without changes
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ from rexecop.connectors.base import ConnectorRequest, ConnectorResponse
4
+ from rexecop.connectors.mock_runtime import MockConnectorRuntime
5
+
6
+
7
+ class TecraxFixtureConnectorRuntime(MockConnectorRuntime):
8
+ """Domain fixture mock for Tecrax offline/bootstrap workflows."""
9
+
10
+ def invoke(self, request: ConnectorRequest) -> ConnectorResponse:
11
+ base = super().invoke(request)
12
+ if base.error and "unsupported mock" not in base.error:
13
+ return base
14
+
15
+ if request.connector == "proxmox" and request.action == "list_vms":
16
+ return ConnectorResponse(
17
+ connector=request.connector,
18
+ action=request.action,
19
+ success=True,
20
+ data={
21
+ "vms": [
22
+ {"id": "vm-101", "name": "zabbix-proxy", "critical": True},
23
+ {"id": "vm-102", "name": "backup-gateway", "critical": True},
24
+ ]
25
+ },
26
+ )
27
+
28
+ if request.connector == "proxmox" and request.action == "restart":
29
+ before_state = {
30
+ "vm_id": "vm-101",
31
+ "agent_status": "running",
32
+ "target": request.target,
33
+ }
34
+ after_state = {
35
+ "vm_id": "vm-101",
36
+ "agent_status": "restarted",
37
+ "target": request.target,
38
+ }
39
+ return ConnectorResponse(
40
+ connector=request.connector,
41
+ action=request.action,
42
+ success=True,
43
+ data={
44
+ "before_state": before_state,
45
+ "after_state": after_state,
46
+ "mutation": "restart_zabbix_agent",
47
+ },
48
+ )
49
+
50
+ if request.connector == "pbs" and request.action == "list_snapshots":
51
+ return ConnectorResponse(
52
+ connector=request.connector,
53
+ action=request.action,
54
+ success=True,
55
+ data={
56
+ "snapshots": [
57
+ {"vm_id": "vm-101", "status": "ok"},
58
+ {"vm_id": "vm-102", "status": "ok"},
59
+ ]
60
+ },
61
+ )
62
+
63
+ return base
64
+
65
+
66
+ def build_runtime() -> TecraxFixtureConnectorRuntime:
67
+ return TecraxFixtureConnectorRuntime()
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from typing import Any
5
+
6
+ from rexecop.execution.backend import StepExecutionContext
7
+
8
+
9
+ def register_handlers() -> Mapping[str, Any]:
10
+ return {
11
+ "environment.resolve_targets": resolve_targets,
12
+ "correlate_vm_backup_coverage": correlate_vm_backup_coverage,
13
+ "capture_agent_state": capture_agent_state,
14
+ "verify_agent_state": verify_agent_state,
15
+ }
16
+
17
+
18
+ def resolve_targets(context: StepExecutionContext) -> dict[str, Any]:
19
+ return {
20
+ "target": context.target,
21
+ "resolved_targets": ["vm-101", "vm-102"],
22
+ }
23
+
24
+
25
+ def correlate_vm_backup_coverage(context: StepExecutionContext) -> dict[str, Any]:
26
+ connector_results = context.shared_state.get("connector_results", {})
27
+ vms: list[dict[str, Any]] = []
28
+ snapshots: list[dict[str, Any]] = []
29
+ for payload in connector_results.values():
30
+ if isinstance(payload, dict):
31
+ vms.extend(payload.get("vms", []))
32
+ snapshots.extend(payload.get("snapshots", []))
33
+ covered = {item.get("vm_id") for item in snapshots}
34
+ rows = []
35
+ for vm in vms:
36
+ vm_id = vm.get("id")
37
+ rows.append(
38
+ {
39
+ "vm_id": vm_id,
40
+ "name": vm.get("name"),
41
+ "backup_status": "ok" if vm_id in covered else "missing",
42
+ }
43
+ )
44
+ result = {
45
+ "rows": rows,
46
+ "all_critical_covered": all(row["backup_status"] == "ok" for row in rows),
47
+ }
48
+ context.shared_state["correlation"] = result
49
+ return result
50
+
51
+
52
+ def capture_agent_state(context: StepExecutionContext) -> dict[str, Any]:
53
+ state = {
54
+ "target": context.target,
55
+ "agent_status": "running",
56
+ "vm_id": "vm-101",
57
+ }
58
+ context.shared_state["agent_before_state"] = dict(state)
59
+ return {"before_state": state}
60
+
61
+
62
+ def verify_agent_state(context: StepExecutionContext) -> dict[str, Any]:
63
+ mutation = context.shared_state.get("mutation_states", {}).get("restart_agent", {})
64
+ after_state = mutation.get("after_state") if isinstance(mutation, dict) else None
65
+ if not isinstance(after_state, dict):
66
+ return {
67
+ "verified": False,
68
+ "reason": "missing restart mutation after_state",
69
+ }
70
+ context.shared_state["agent_after_state"] = dict(after_state)
71
+ return {
72
+ "verified": after_state.get("agent_status") == "restarted",
73
+ "after_state": after_state,
74
+ "before_state": context.shared_state.get("agent_before_state"),
75
+ }
@@ -0,0 +1,7 @@
1
+ connector:
2
+ name: pbs
3
+ capabilities:
4
+ - list_snapshots
5
+ modes:
6
+ - read_only
7
+ timeout_default: 20s
@@ -0,0 +1,9 @@
1
+ connector:
2
+ name: proxmox
3
+ capabilities:
4
+ - list_vms
5
+ - restart
6
+ modes:
7
+ - read_only
8
+ - apply
9
+ timeout_default: 10s
@@ -0,0 +1,7 @@
1
+ intent:
2
+ id: check_backup_status
3
+ workflow: workflows/check_backup_status.yaml
4
+ risk: low
5
+ modes:
6
+ - read_only
7
+ - dry_run
@@ -0,0 +1,6 @@
1
+ intent:
2
+ id: restart_zabbix_agent
3
+ workflow: workflows/restart_zabbix_agent.yaml
4
+ risk: medium
5
+ modes:
6
+ - apply
@@ -0,0 +1,30 @@
1
+ profile_contract:
2
+ name: tecrax
3
+ version: "0.3.1"
4
+
5
+ intents:
6
+ required: true
7
+
8
+ workflows:
9
+ required: true
10
+
11
+ connector_requirements:
12
+ required: true
13
+
14
+ risk_classes:
15
+ required: true
16
+
17
+ evidence_requirements:
18
+ required: true
19
+
20
+ governance_expectations:
21
+ required: true
22
+
23
+ validation_rules:
24
+ required: true
25
+
26
+ rollback_rules:
27
+ optional: true
28
+
29
+ escalation_rules:
30
+ required: true
@@ -0,0 +1,13 @@
1
+ validation_rule:
2
+ intent: check_backup_status
3
+ steps:
4
+ - type: require_mapping
5
+ key: correlation
6
+ fail:
7
+ rule: check_backup_status.correlation_required
8
+ details:
9
+ reason: missing correlation result
10
+ - type: require_truthy_path
11
+ path: correlation.all_critical_covered
12
+ pass_rule: check_backup_status.all_critical_covered
13
+ details_from: correlation
@@ -0,0 +1,18 @@
1
+ validation_rule:
2
+ intent: restart_zabbix_agent
3
+ steps:
4
+ - type: require_mapping
5
+ key: agent_after_state
6
+ fail:
7
+ rule: restart_zabbix_agent.after_state_required
8
+ details:
9
+ reason: missing agent_after_state
10
+ - type: require_equals
11
+ path: agent_after_state.agent_status
12
+ value: restarted
13
+ pass_rule: restart_zabbix_agent.agent_restarted
14
+ details:
15
+ agent_after_state: $agent_after_state
16
+ mutation_states: $mutation_states.restart_agent
17
+ before_state: $mutation_states.restart_agent.before_state
18
+ after_state: $mutation_states.restart_agent.after_state
@@ -0,0 +1,42 @@
1
+ workflow:
2
+ id: tecrax.check_backup_status
3
+ intent: check_backup_status
4
+ mode: read_only
5
+ risk: low
6
+ description: Check backup coverage for critical VMs using profile-defined connectors.
7
+
8
+ steps:
9
+ - id: resolve_inventory
10
+ type: internal
11
+ action: environment.resolve_targets
12
+ pause_safe: true
13
+
14
+ - id: query_proxmox
15
+ type: connector
16
+ connector: proxmox
17
+ action: list_vms
18
+ timeout: 10s
19
+ pause_safe: false
20
+
21
+ - id: query_pbs
22
+ type: connector
23
+ connector: pbs
24
+ action: list_snapshots
25
+ timeout: 20s
26
+ pause_safe: false
27
+
28
+ - id: correlate
29
+ type: internal
30
+ action: correlate_vm_backup_coverage
31
+ pause_safe: true
32
+
33
+ - id: produce_receipt
34
+ type: evidence
35
+ action: produce_receipt
36
+ pause_safe: true
37
+
38
+ retry:
39
+ max_attempts: 2
40
+ allowed_on:
41
+ - timeout
42
+ - transient_connector_error
@@ -0,0 +1,45 @@
1
+ workflow:
2
+ id: tecrax.restart_zabbix_agent
3
+ intent: restart_zabbix_agent
4
+ mode: apply
5
+ risk: medium
6
+ description: Restart Zabbix agent on a target VM using mock Proxmox connector.
7
+
8
+ steps:
9
+ - id: capture_state
10
+ type: internal
11
+ action: capture_agent_state
12
+ pause_safe: true
13
+
14
+ - id: restart_agent
15
+ type: connector
16
+ connector: proxmox
17
+ action: restart
18
+ timeout: 30s
19
+ pause_safe: false
20
+
21
+ - id: verify_state
22
+ type: internal
23
+ action: verify_agent_state
24
+ pause_safe: true
25
+
26
+ - id: produce_receipt
27
+ type: evidence
28
+ action: produce_receipt
29
+ pause_safe: true
30
+
31
+ retry:
32
+ max_attempts: 2
33
+ allowed_on:
34
+ - timeout
35
+ - transient_connector_error
36
+ blocked_on:
37
+ - policy_denied
38
+
39
+ rollback:
40
+ mode: dry_run
41
+ steps:
42
+ - id: rollback_marker
43
+ type: internal
44
+ action: record_rollback_marker
45
+ pause_safe: true
@@ -29,12 +29,13 @@ def test_cli_status_keeps_local_fixture_posture(capsys) -> None:
29
29
  assert main(['status']) == 0
30
30
 
31
31
  stdout = capsys.readouterr().out
32
+ assert '0.3.1-alpha' in stdout
32
33
  assert 'local-fixture profile' in stdout
33
34
  assert 'no live infrastructure' in stdout
34
35
 
35
36
 
36
37
  def test_version_and_public_truth_validator_agree() -> None:
37
- assert __version__ == '0.2.2a0'
38
+ assert __version__ == '0.3.2a0'
38
39
  result = subprocess.run(
39
40
  [sys.executable, str(ROOT / 'scripts' / 'validate_public_truth.py')],
40
41
  cwd=ROOT,
@@ -43,6 +44,8 @@ def test_version_and_public_truth_validator_agree() -> None:
43
44
  check=True,
44
45
  )
45
46
  assert result.stdout.strip() == (
46
- 'public_truth_ok:tecrax==0.2.2a0:'
47
- 'govengine>=0.11.0a0,<0.12:sclite-core>=0.8.0a0,<0.9'
47
+ 'public_truth_ok:tecrax==0.3.2a0:'
48
+ 'govengine>=0.12.2a0,<0.15:'
49
+ 'sclite-core>=1.0.1,<1.1:'
50
+ 'rexecop>=0.2.2a0,<0.3'
48
51
  )
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib.metadata import entry_points
4
+
5
+ from tecrax.connectors.proxmox import build_http_api_connector_config, merge_http_api_connector_config
6
+ from tecrax.connectors.proxmox_runtime import build_connector_runtime
7
+
8
+
9
+ def test_tecrax_proxmox_connector_backend_entry_point() -> None:
10
+ eps = entry_points(group="rexecop.connector_backends")
11
+ names = {ep.name for ep in eps}
12
+ assert "tecrax_proxmox" in names
13
+
14
+
15
+ def test_merge_http_api_connector_config_drops_secret_ref_when_base_url_set() -> None:
16
+ template = build_http_api_connector_config(staging_paths=True)
17
+ merged = merge_http_api_connector_config(
18
+ template,
19
+ {"base_url": "http://example.test"},
20
+ )
21
+ assert merged["base_url"] == "http://example.test"
22
+ assert "base_url_secret_ref" not in merged
23
+
24
+
25
+ def test_build_connector_runtime_returns_invokeable_backend() -> None:
26
+ runtime = build_connector_runtime(
27
+ connector_name="proxmox",
28
+ config={"staging_paths": True, "base_url": "http://127.0.0.1:9"},
29
+ profile_root=None,
30
+ mutating_allowed=False,
31
+ )
32
+ assert hasattr(runtime, "invoke")
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from tecrax.connectors.proxmox import build_http_api_connector_config
4
+
5
+
6
+ def test_build_proxmox_http_api_config_staging_paths() -> None:
7
+ config = build_http_api_connector_config(staging_paths=True)
8
+ assert config["backend"] == "http_api"
9
+ assert config["actions"]["list_vms"]["pagination"]["items_path"] == "data.vms"
10
+ assert config["retry"]["base_delay"] == 0.2
11
+
12
+
13
+ def test_build_proxmox_http_api_config_production_paths() -> None:
14
+ config = build_http_api_connector_config(staging_paths=False)
15
+ assert "/api2/json/cluster/resources" in config["actions"]["list_vms"]["path"]
16
+ assert config["actions"]["list_vms"]["unwrap"] == "data"
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib.metadata import entry_points
4
+ from pathlib import Path
5
+
6
+ import tecrax
7
+ from tecrax import profile_root
8
+
9
+
10
+ def test_profile_root_points_to_bundled_directory() -> None:
11
+ root = Path(profile_root())
12
+ assert root.is_dir()
13
+ assert (root / "profile.yaml").is_file()
14
+ assert (root / "intents").is_dir()
15
+ assert (root / "workflows").is_dir()
16
+
17
+
18
+ def test_profile_yaml_loads_with_expected_contract() -> None:
19
+ profile_path = Path(profile_root()) / "profile.yaml"
20
+ text = profile_path.read_text(encoding="utf-8")
21
+ assert "name: tecrax" in text
22
+ assert 'version: "0.3.1"' in text
23
+
24
+
25
+ def test_rexecop_profiles_entry_point_registered() -> None:
26
+ eps = entry_points(group="rexecop.profiles")
27
+ names = {ep.name for ep in eps}
28
+ assert "tecrax" in names
29
+ tecrax_ep = next(ep for ep in eps if ep.name == "tecrax")
30
+ assert tecrax_ep.value == "tecrax:profile_root"
31
+ assert Path(tecrax_ep.load()()).is_dir()
32
+
33
+
34
+ def test_package_version_matches_profile_bundle() -> None:
35
+ assert tecrax.__version__ == "0.3.2a0"
@@ -1,23 +0,0 @@
1
- # Tecrax Public Status
2
-
3
- Tecrax is an **alpha local-fixture infrastructure-operations profile package** over GovEngine and SCLite.
4
-
5
- ## Current Truth
6
-
7
- - Source version: `0.2.2a0`.
8
- - Public release label: `0.2.2-alpha`.
9
- - Dependency chain: `tecrax -> govengine>=0.11.0a0,<0.12 -> sclite-core>=0.8.0a0,<0.9`.
10
- - Runtime posture: dry-run/local-fixture only.
11
- - Public surface: `tecrax fixture-review --service demo-web`.
12
-
13
- The `0.2.2-alpha` line is dependency/conformance synchronization only; it
14
- keeps the fixture-only posture unchanged.
15
-
16
- ## Non-Claims
17
-
18
- - no live infrastructure connection;
19
- - no host inventory ownership;
20
- - no credential, key-store, PKI, or KMS ownership;
21
- - no carrier adapter implementation;
22
- - no scheduler/storage/queue persistence ownership;
23
- - no production operational readiness.
@@ -1,7 +0,0 @@
1
- """Tecrax dry-run local-fixture infrastructure-operations profile."""
2
-
3
- from .local_fixture import build_local_fixture_review
4
-
5
- __version__ = "0.2.2a0"
6
-
7
- __all__ = ['__version__', 'build_local_fixture_review']
File without changes
File without changes