Add a stateful Proxmox API console and broad handler coverage beyond the

initial QEMU slice, backed by imported contracts for majors 6–9.
- Implement durable handlers for access/auth, cluster, LXC, storage, HA,
  firewall, Ceph, SDN, ACME, notifications, pools, mapping, and node ops
- Serve an interactive Web UI with catalog browsing, demo seed controls,
  and OpenAPI/help surfaces
- Bundle PVE 6.4-15, 7.4-16, and 8.4.5 contract revisions alongside 9.2.3
- Support in-memory runtime contract Apply (POST /ui/api/contract/apply)
  so /version and /api2 routes follow the selected major until restart
- Expand seed profiles (including demo-cluster), migrations 007–008, TLS
  gateway config, Compose/Makefile tooling, and compatibility evidence
- Tighten .gitignore for macOS, hidden directories (.*/), and local secrets
This commit is contained in:
Sergey Antropoff
2026-07-16 01:08:01 +03:00
parent 003ee5d634
commit 777926487b
189 changed files with 241501 additions and 944 deletions
+49 -2
View File
@@ -45,6 +45,8 @@ class MethodEvidence(BaseModel):
verb: str
dimensions: tuple[CompatibilityDimension, ...]
sources: tuple[str, ...]
observed: bool = True
verified: bool = True
@field_validator("sources")
@classmethod
@@ -53,6 +55,11 @@ class MethodEvidence(BaseModel):
raise ValueError("evidence record requires at least one source")
return value
@field_validator("verb")
@classmethod
def normalize_verb(cls, value: str) -> str:
return value.upper()
class EvidenceManifest(BaseModel):
model_config = ConfigDict(frozen=True, extra="forbid")
@@ -74,18 +81,58 @@ class EvidenceManifest(BaseModel):
dimension: set() for dimension in CompatibilityDimension
}
for record in self.records:
key = (record.path, record.verb)
key = (record.path, record.verb.upper())
for dimension in record.dimensions:
evidence[dimension].add(key)
return MappingProxyType(
{dimension: frozenset(methods) for dimension, methods in evidence.items()}
)
def observed_methods(self) -> frozenset[MethodKey]:
return frozenset(
(record.path, record.verb.upper()) for record in self.records if record.observed
)
def verified_methods(self) -> frozenset[MethodKey]:
return frozenset(
(record.path, record.verb.upper()) for record in self.records if record.verified
)
def load_evidence_manifest(path: Path) -> EvidenceManifest:
return EvidenceManifest.model_validate_json(path.read_bytes())
def evidence_dir(settings: object | None = None) -> Path:
"""Directory that holds per-version ``pve-{version}.json`` ledgers."""
evidence = getattr(settings, "compatibility_evidence", None) if settings is not None else None
if isinstance(evidence, Path) and evidence.name:
return evidence.resolve().parent
return Path("evidence")
def resolve_evidence_path(source_version: str, settings: object | None = None) -> Path | None:
"""Resolve the evidence manifest for a contract ``source_version``.
Preference order:
1. ``evidence/pve-{source_version}.json`` next to the configured evidence file
(or ``./evidence`` when unset)
2. ``settings.compatibility_evidence`` when its embedded ``source_version`` matches
"""
candidate = evidence_dir(settings) / f"pve-{source_version}.json"
if candidate.is_file():
return candidate
configured = getattr(settings, "compatibility_evidence", None) if settings is not None else None
if not isinstance(configured, Path) or not configured.is_file():
return None
manifesto = load_evidence_manifest(configured)
if manifesto.source_version == source_version:
return configured
return None
@dataclass(frozen=True, slots=True)
class CompatibilityReport:
source_version: str
@@ -233,7 +280,7 @@ def build_report(
regressions: frozenset[MethodKey] = frozenset(),
) -> CompatibilityReport:
declared = frozenset(
(path.path, method.verb) for path in snapshot.paths for method in path.methods
(path.path, method.verb.upper()) for path in snapshot.paths for method in path.methods
)
for name, evidence in {
"implemented": implemented,