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:
+49
-2
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user